Python-网络自动化入门指南-全-

# Python 网络自动化入门指南(全) > 原文:[Introduction to Python Network Automation](https://zh.book4you.org/book/14995492/b423d3) > > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) # 一、Python 网络自动化简介 这一章是这本书的入门,讨论了在今天的 it 行业中成为一名 IT 专业人员的感觉。我们还将讨论不同的企业 IT 工程领域组及其职责。然后,这一章比较了每个领域组的优缺点,并起草了一份工作研究计划,作为这本书的基础。本章还将讨论为什么你可能想学习一门编程语言,在我们的例子中是 Python,因为这是你购买这本书并开始你的网络自动化之旅的主要原因。最后,我们将讨论在单台 PC 上构建一个完整的 Python/Linux/网络自动化实验室的最低 PC/笔记本电脑要求。所有实验室机器、Linux 服务器、路由器和交换机将使用推荐的软件集安装在一台 PC/笔记本电脑上。 ![img/492721_1_En_1_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_1_Figa_HTML.jpg) ## 奠定基础 近年来,网络可编程性概念已经席卷了企业网络行业,网络自动化已经成为风暴的焦点有几年了。在基于传统 IT 框架的传统 IT 生命周期管理上花费数百万美元的企业和组织一直在寻找一种新的 IT 框架,它将提供更稳定和可预测的网络运营,而不会中断其 IP 服务和最终用户应用。相当多的网络工程师已经开始探索网络自动化,剩下的工程师还在努力起步。然而,许多人正面临各种挑战。回顾我自己的网络自动化之旅,我知道这可能是一个缓慢、痛苦和令人生畏的经历。 无论您在职业生涯中从未接触过编程语言,还是刚接触软件定义的网络,开始网络自动化之旅都感觉像是在试图爬上一座大山。一旦你到达第一座山峰,你可能会意识到还有更大的山峰在等着你。你甚至可能想要放弃。 在过去的几十年里,作为一名网络工程师,你可能一直过着舒适的生活。现在你的老板希望你提高你并不存在的编程技能,这样你就可以用代码行代替手工任务,这样你就可以为你的公司增加更多的价值。如果你说不,你的老板有一个 B 计划:别人会为你的团队编写代码来自动化你的工作,你会开始失去信誉(甚至可能丢掉工作)。您需要走出舒适区,开始我们的网络可编程性之旅。对于大多数传统的网络工程师来说,网络可编程性仍然是未知的领域。 在本章中,我们将确定当今典型 IT 环境中的三个主要 IT 领域组。然后,我们将定义每个 IT 领域组拥有的通用 IT 技能集,以确定每个组的相关优势和劣势。这本书是由网络工程师为网络工程师编写的,并由 Cisco Network Academy 讲师审阅。优势和劣势讨论将从企业网络行业的角度进行。重点是学习网络组如何使用现实的学习策略来拉近与其他两个组(DevOps 和 systems 组)的距离,并成长为跨职能的工程师,他们拥有强大的网络技能以及管理 Linux 操作系统和编写代码以开发业务应用的技能集。本章还介绍了一个跨职能的“混合工程师”的概念,这将很快在 IT 就业市场的巨大需求。这种混合工程师的职业知识增长将采取一个 *T* 的形式,因为他们在其主要的 IT 领域有一个稳固的立足点,并扩展到其他领域的技能组合,作为字母 *T* 的顶端。 在本书的几乎每一章中,除了这一章,您将从您的 PC 或笔记本电脑上安装、配置、练习、编码和学习,因此您的系统的最低要求是您用本书学习 Python 网络自动化的成功。因此,我们将回顾您的系统的最低系统要求,并向您介绍用于测量本书中概述的关键任务大小的软件。我们还将简要介绍用于 Python 网络自动化开发的集成开发环境(IDE ),并提供本书中使用的软件、源代码和文件的所有下载链接。在本章结束时,你会更好地了解当前网络工程师的优势和劣势。因此,您将知道在开始编写 Python 代码以开发您自己的网络自动化应用时必须关注的差距(目标研究领域)。 ## 探索你的技能和先决条件 本节讨论了当今工作中的三个主要 IT 领域组的技能集。你将了解每组的优势和劣势。从网络小组的角度来看,我们将讨论和剖析你的技能组合中的弱点,并制定可行的学习策略来解决这些弱点,并将它们转化为你的优势。学习 Python 语法和概念将帮助您实现 25%的网络自动化目标。那剩下的 75%呢?要编写 Python 网络自动化代码,除了网络之外,你还必须在许多其他领域变得强大,本章将帮助你在这些领域变得更好。当然,我们还将从网络的角度介绍 Python 基础知识。我们将一起制定一个学习计划,以解决常见网络工程师的弱点,并使用 Python 引导您朝着网络自动化的正确方向前进。 如果你目前在 IT 行业工作,特别是在企业级路由、交换和安全技术方面,你应该为你的工作感到自豪。你可能会联想到图 1-1 中的图像。虽然这个形象可能会让你发笑,但你可能已经见过一个 IT 工程师,他走路和说话的样子好像他什么都知道。我们都遇到过这样的情况,客户希望我们作为技术专家了解一切。 ![img/492721_1_En_1_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_1_Fig1_HTML.jpg) 图 1-1。 IT 工程师 可悲的是,图 1-1 中的图像揭示了一些 IT 工程师在工作中的想法和行为。自从 1971 年第一台个人电脑发明以来,随着不同技术的出现和消失,许多 IT 工作来了又去。随着新技术引入 IT 市场,大多数企业 IT 生态系统都在不断发展。不幸的是,一些 IT 工程师过于固执,无法与时俱进,经常陷入技术转型期,导致他们的 IT 职业生涯提前终止。目前,人工智能(AI)和 IT 自动化的时代已经在企业网络 IT 行业到来。如果你拒绝提高技能,你的职位可能不像你想象的那么稳固。 事实上,多年来,IT 行业一直试图通过将技能较低的工作转移到 IT 运营成本相对低于本土的发展中国家来降低运营成本。多年来,降低运营成本和减少人力资源开销是许多组织的趋势。我们生活在一个时代,人力资源是商业模式中的间接成本,如今许多客户希望支付更少的费用,但仍然要求高质量的 IT 交付服务。与许多组织的说法相反,在发达国家,许多本地 IT 工作被视为间接费用,而不是组织的宝贵人力资源。也就是说,许多 IT 组织声称他们重视他们的员工,他们优先考虑的是这些人力资源在组织的 IT 运营中的福祉。然而,当关键时刻到来时,几乎每个组织都会屈服于财务压力,削减 IT 运营成本,以最大化组织的利润。在过去的 20 年里,IT 外包和离岸外包在某种程度上取得了巨大的成功。尽管如此,每个 IT 部门都比以前更加努力地降低运营成本,这是加速采用软件定义的网络和基础架构作为代码等 IT 自动化的催化剂。 ### 三个主要 IT 领域组的一般能力 印度班加罗尔俗称印度硅谷,那里有句俗语:“IT 职业有两种人;一是 IT 专业人员,二是管理这些人的专业人员。”在本书中,为了帮助您理解,我们根据每个组的能力和特征将 IT 领域组分为三个不同的组。然后,我们将对他们的一般技术能力进行相互比较,以预测某个特定的工程师组,即网络工程(连接)组在不久的将来的 IT 行业会是什么样子。未来五年及以后,谁会走在网络领域的最前沿?让我们回顾一下差距和要求,展望未来,并制定一个学习 Python、网络自动化和任何其他要求的计划,以建立您在 IT 方面提高技能的信心。 在企业级别,许多组织有三个主要的领域组负责 IT 基础设施。 * *网络*:网络组,有时也称为连接组,负责所有基础设施和最终用户连接。 * *系统*:系统组负责服务器和应用,以服务的形式向其他基础设施或最终用户提供服务;其中包括对公司成功至关重要的企业电子邮件、共享存储和各种业务应用。 * 最后一组是软件工程师或程序员,他们测试、开发和实现各种业务应用,以满足公司的业务需求。最近,这些工程师被称为 DevOps 工程师。 为了简化本书,我们将这三个主要的域组称为*网络组*、*系统组*和*开发运维组*。网络组致力于 IP 连接和服务,包括 RS、安全、数据中心、云和统一协作工程师等技术子领域。网络连接是任何企业业务连接的基础,我们认为应用和服务都是连接网络的租户。因此,如果公司网络出现重大网络故障,整个业务就会停止。 系统组负责在 Windows 和 Linux 操作系统上运行的关键业务应用和操作系统。 DevOps 小组由软件开发人员和程序员组成,他们专门使用各种编程语言和软件开发工具开发软件和应用。通过比较和分析每个组当前的能力,我们将更好地了解每个组的优势和劣势。因此,我们确定了网络组中的几个弱点,以便您可以针对这些弱点开展工作。尽管本书中使用的网络供应商技术来自思科,但请注意,本书中展示的网络自动化概念将适用于任何供应商的网络和安全技术,包括思科、Arista、Juniper、Check Point、Palo Alto 和惠普。 首先,让我们比较一下这三个领域组之间存在哪些技术能力差异。为了分析每个小组的能力,我们将使用蜘蛛网图来绘制和说明不同的技术能力。在这个简单的比较之后,我们将推荐使用 Python 作为首选编程语言来实现网络自动化的方法。 为了便于比较并帮助您理解三个 IT 领域组的能力,让我们首先在图表上绘制每个组的能力,如图 1-2 所示。该图表使用十个分数来表示图表上绘制的所有能力。例如,一个领域的胜任水平将是十分中的八分,一个能力较差的人将得到十分中的两分。 ![img/492721_1_En_1_Fig2_HTML.png](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_1_Fig2_HTML.png) 图 1-2。 IT 领域团队的一般能力比较 该图展示了过去十年中每个工程团队所需的通用能力。在这样的图表上绘制共同的能力已经让我们对每个组的优势和劣势有了一个清晰的认识。传统上,网络工程师对学习软件工程或编写代码没有严格的要求,因为工作节奏比今天的网络需要慢得多。如前所述,大多数工作都是由坐在计算机前的传统网络工程师通过命令行完成的。系统组因为必须批量(批量)执行大部分任务的工作,接触了更多的系统自动化工具和语言。从网络团队的角度来看,编写代码来自动化企业网络直到五年前还是一个新概念。因此,网络工程师并不急于向系统或 DevOps 领域扩展他们的技能。对于一个自动化企业网络的人来说,他必须拥有所有这三个组的组合。从图中可以看出,这三个组都不具备实现企业网络自动化的所有能力。如果您来自传统的网络工程背景,那么可以有把握地猜测,您将很少接触管理企业系统或参与企业工具(应用)开发项目。最近,每个人都被期望为客户提供更多物有所值的东西,并获得良好的客户(最终用户)体验。 这种同样的心态也影响到了网络团队的工作。尽管如此,你不能像在离岸外包的方法中那样投入更多的人来提供服务水平,也不能偷工减料来弥补数字,因为许多销售业务承诺更多,但只提供最低限度的服务。此外,如图所示,大多数公司现在意识到,没有一个团队具备为公司提供企业网络自动化解决方案的所有能力。公司不想支付运行所有三个 IT 领域组的开销;因此,当前的重点是提升现有员工的技能,以实现公司的 IT 自动化。IT 业内人士一直在说,“不同 IT 领域的墙正在倒塌。”现在,网络工程师必须知道如何管理 Linux 操作系统,具有管理编程语言的技能,并且必须知道如何开发特定的应用来自动化他们所管理的企业网络。另一方面,任何来自系统或 DevOps 组的工程师都可以学习网络技术并自动化网络组的工作。 当然,该图包含了每个 IT 领域组的一般能力,每个组中每个工程师的能力因人而异。尽管如此,这个图表为我们提供了网络组方法应该在哪里以及如何处理 Python 网络自动化研究的准确指针。 ### IT 工程师职责的比较分析 在图 1-2 中,您看到了三个 IT 领域组的能力。在这一节中,我们将看看每组工程师的典型职责,以确定网络工程师在使用 Python 进行企业网络自动化方面需要改进的地方。花大约五分钟时间研究每个小组的职责,您可以很容易地看到传统的网络工程师必须集中在哪里,以获得编写脚本化应用来自动化他们的工作所需的技能。如阴影区域所示,第一要求是掌握 Linux、Windows 等操作系统;这是强大的网络技能的基础。 ![img/492721_1_En_1_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_1_Fig3_HTML.jpg) 图 1-3。 IT 工程师的一般职责 对于企业自动化,使用的默认操作系统是 Linux。在熟悉了初级到中级的 Linux 管理之后,您可以从基本的 Python 语法开始,慢慢地发现 DevOps 是如何工作的。同时,您需要接触 Linux grep,或者更好的是,通用正则表达式,因为数据处理是任何编程语言的关键。从这一点开始,你可以逐渐扩展到其他感兴趣的领域。 如前所述,您需要具备思科 CCNA 的工作知识,以便从本书中获得最大收益,因此,如果您正在为 CCNA 学习或已经完成学业,您将从本书中获得更多。虽然本书的内容并不教授思科系统公司的一般网络概念,但本书的大部分内容都是实践操作,后面章节中的实验都是基于思科的 IOS 和 IOS XE 配置。即使你不是思科认证网络工程师,我也会根据图 1-2 和 1-3 中的讨论来编写这本书的内容,因此主题广泛,包含许多有助于提升你一般 IT 技能的部分。 基于图 1-3 ,让我们利用网络工程师使用 Python 为网络自动化做准备所需的技能集。以下内容源自系统和 DevOps 工程师的职责,但不是典型网络工作的核心职责: * 系统安全维护(凭证、系统和用户安全访问控制、数据访问控制) * 硬件和软件生命周期管理(服务器和用户操作系统以及特定于操作系统的应用) * 开发运维、服务器设置和部署 * 数据库和网络服务等应用支持 * 数据库设计和设置 * 后端开发服务器端脚本(开发复杂软件) * 后端服务器端脚本 * 使用 HTML、CSS 和 JavaScript 进行前端开发 * 软件测试(您自己的或供应商的) 之前的职责相当笼统宽泛。许多子域技术在前面的列表中甚至没有提到。但是从一个网络工程师的角度来看,虽然在网络基本职责上有一个坚实的基础,但是你应该意识到你必须将你的兴趣和技能集转移到系统和开发组的职责上。为了让您的生活更轻松,请参见表 1-1 。如前所述,本书不是网络核心概念书,也不是 Python 语法和概念书,更不是 Linux 核心概念书。这本书是三本书的结合,但是主题是有选择的,以帮助你为 Python 网络自动化做好准备。你将在本书中学到的技能将作为你知识的基础。 当您阅读每一章时,您将获得超出舒适区的新 IT 技能。可以看到表 1-1 所示的技术途径是实用的,包括安装虚拟机、安装网络服务、学习 Linux 和 Python 基础、安装 Python、通过编写 Python 代码控制联网设备。除非你是一名售前工程师或 IT 领域的架构师,否则你需要成为一名真正的专业人士,能够从事真正的实际工作。下表显示了网络工程师开始 Python 网络自动化之旅的一种方法。 表 1-1。 对开始学习 Python 网络自动化的网络工程师的技能建议 | 所需技能 | 推荐的研究主题 | | --- | --- | | **Python 基础知识** | 在计算机上安装 Python 学习基本的 Python 语法将基本 Python 语法关联并应用于 Python 网络上下文 | | **VMware 和虚拟化基础知识** | 安装最新的 VMware Workstation Pro 版本了解虚拟化基础知识学习构建虚拟机 | | **操作系统管理、修补、升级、故障排除和配置技能** | 安装两种不同风格的 Linux,Ubuntu 20.04 LTS 和 CentOS 8.1 使用 VMware 模板安装预留的服务器;导入 GNS3 虚拟机服务器阅读 Python 安装指南并解决任何安装问题 | | **学习 Linux 基础知识** | 了解 Linux 目录了解 Linux 文件管理和实践了解如何在没有 GUI 桌面应用的情况下使用 vi 和 nano 文本编辑器 GUI 桌面 Linux 远程连接概念,SSH,Telnet,基本 API 安装 FTP、SFTP、TFTP 和 NTP 服务器 | | **正则表达式** | 学习正则表达式基础知识了解如何在 Python 中使用正则表达式学习将正则表达式与网络思想和概念联系起来并加以应用 | | **网络仿真器(GNS3)技能** | 学习安装和集成 GNS3 学习配置基本到复杂的网络拓扑学习将 GNS3 与 VMware Workstation 集成 | | **思科 IOS 和 IOS XE 命令行** | 学习将思科 IOS、IOS-XE 和 CML-Personal L2 和 L3 映像集成到 GNS3 学习网络自动化实验设置的基本路由器和交换机命令编写 Python 代码,通过 SSH、Telnet、FTP 和 SNMP 控制仿真的 Cisco 网络设备在虚拟实验室中练习 Python 脚本使用 Python 脚本安装和设置虚拟路由器和交换机 | | **应用开发和建立** | 安装 Python 3 安装 Python Telnet 和 SSH 模块使用 Python 代码了解网络自动化的应用开发理解基本的 DevOps 开发概念 | | **特定工程师的任务自动化** | 了解任务流以自动化特定任务 | 表 1-1 中推荐的研究主题构成了本书的基础。在这本书的不同点上,你将被介绍到不同的技术和基于锻炼的任务,这需要你在你的 PC(或笔记本电脑)前跟随书的内容。为了完整地完成这本书,你必须完成每一章的所有练习,以获得足够的实践,并建立使用 Python 实现网络自动化所需的各种技能。我想让你在一个地方学基础,学好。 ## Python 和网络自动化研究 为什么要学 Python 做网络自动化?这个问题可能很难回答。答案可能因人而异。尽管如此,学习 Python 进行网络自动化的共同目标可能是将物理上重复的任务转换成代码行,以便您可以专注于工作中更有价值和更重要的任务。使用 Python 的网络自动化过程可能与使用 Ansible 或其他设备管理工具的网络自动化过程非常不同。你可以自由地构建你的思维和任务过程,同时尝试自动化实际的物理任务和以自动化为目标的思维过程。那么,精简思维过程意味着什么呢?也许您正在谈论使用 Python 代码更有效、更系统、更快速地执行您的工作。 你是在暗示你和你的团队这些年来所做的事情是低效的、缓慢的、无组织的吗?也许这就是你在这里读这本书的原因。如果是这样,首先回顾一下你的团队正在进行的效率低下的工作,将每个逻辑思维过程和物理任务分解成步骤。使用 Python 代码行来确定您试图自动化的内容是非常方便的。这将为您开始第一个网络自动化项目和应对当前的自动化挑战奠定坚实的基础。 与其开始 Python 研究,不如四处看看,确定工作中需要自动化的手动密集型任务,然后记录当前流程。现在,您可以开始学习 Python 语法了。这个简单的过程会让你保持高度的动力,也让你专注于你的第一次 Python 学习,因为你不是为了学习目前最流行的编程语言之一而学习 Python。你学习它是有目的的。 ### 为什么有人要学 Python? Python 被领先的 IT 服务供应商和企业使用,如 Google、YouTube、Instagram、网飞、Dropbox 和 NASA。Python 编程有很多领域可以在短时间内开发出来,并应用于解决实际问题。学习它的障碍是当今使用的所有主要编程语言中最低的。 根据 [RedMonk。com](http://redmonk.com) ,2020 年 6 月,Python 成为全球第二大流行的编程语言,超越 Java,势头越来越猛。每种编程语言都有优点和缺点,Python 也不能幸免于各种缺点,但为什么它会变得如此流行呢?2019 年 10 月,Python 甚至超越 JavaScript 成为 Stack Overflow 上被问得最多的语言。 那么,如何开始呢?您必须很好地掌握基本的 Python 语法和概念。有许多关于 Python 基础和概念的书籍,其中一些非常优秀,但我在学习时仍然无法将 Python 概念和网络自动化概念联系起来。奇怪的是,也有中级到高级的 Python 网络自动化书籍,但它们没有解释从哪里开始,以及要达到中级到高级的水平需要做哪些练习。每个人都告诉你 Python 是一门非常容易学习的语言,入门门槛很低,但是他们没有告诉你,如果你想在任何一门编程语言上变得优秀,你需要埋头苦干,对你选择的语言保持执着和热情。准备好放弃你的社交之夜和周末来学习一些愚蠢的 Python 库特性,观看数百小时的所谓 Python 大师的 YouTube 视频和在线培训视频。 这是正确的;入门门槛低并不意味着 Python 是最容易学习的语言。意思是任何人都可以开始学习 Python 这种编程语言,但是有多少人有足够的毅力和热情把编写 Python 代码当成自己的生命?学习 Python 最具挑战性的部分通常是坚持不懈地推动自己学习使用 Python 做事情的不同方法,并尽可能保持热情。要做到这一点,你必须找到对你和你的工作重要的个人的实际用例及小项目。请记住,自动化是一个缓慢而稳定的过程,因为您想要自动化的一切都必须写入代码行。某人或某事(在人工智能的情况下)必须写代码。没有足够好的人工智能来模仿一个有经验和逻辑的网络工程师。因此,网络工程师必须自动化任务,直到人工智能(AI)能够模仿和自动化网络工程师的行为。 总之,图 1-4 来自 2019 年堆栈溢出调查。Python 是增长最快的编程语言,连续第三年成为最受欢迎的语言,这意味着尚未使用它的开发人员表示,他们希望学习 Python 来开发应用。 ![img/492721_1_En_1_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_1_Fig4_HTML.jpg) 图 1-4。 2019 年开发者最想用的编程语言来源: [` insights。斯塔科弗洛。com/survey/2019 #最喜欢最害怕最想要的`](https://insights.stackoverflow.com/survey/2019%2523most-loved-dreaded-and-wanted) 图 1-5 显示了 2017 年各大编程语言的增长情况。 ![img/492721_1_En_1_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_1_Fig5_HTML.jpg) 图 1-5。 基于堆栈溢出问题的各大编程语言成长,2017 来源: [` stackoverflow。博客/ 2017/ 09/ 06/不可思议-成长-python/`](https://stackoverflow.blog/2017/09/06/incredible-growth-python/) ## 用 Python 学习网络自动化需要什么? 大多数研究网络自动化的网络工程师都会同意,拥有一个灵活的集成开发环境(IDE)将有助于工程师开发应用。有许多方法可以学习基本的 Python 语法,但并不是每个人都有该公司提供的网络自动化开发实验室环境,尽管 Cisco dCloud 或类似的在线沙盒提供了定时实验室环境。尽管如此,你并不能随时随地按需得到它。所以,我建议你用自己的设备来控制你的实验室。这种实验室环境可以通过多种形式进行配置。首先,它可以使用路由器、交换机和服务器配置 100%的硬件。第二,它可以是部分硬件和部分虚拟化的混合开发环境;第三,它可以是 100%虚拟化的设备,位于服务器或 PC 内。第一种方法需要大量的初始投资来购买二手设备,并产生持续的电费来保持物理设备的运行。混合开发环境将变得很麻烦,一些设备在物理设备上运行,一些设备在虚拟化环境上运行。最后一个环境对于学习 Python 进行网络自动化的人来说是理想的实验室环境。这是通过集成运行在虚拟化解决方案上的多个系统和网络操作系统实现的。实践和体验真实网络环境中发生的一切几乎是不可能的,但建立集成开发实验室将是最接近的事情。如果你通过书本学习一切,或者通过狂看 YouTube 视频被动地学习 Python 网络自动化,那是不够好的。构建您的 IDE 并键入您的代码来学习和掌握 Python。 要通过这本书学习 Python 网络自动化,您需要的只是一台相当强大的台式机或笔记本电脑,它具有足够的中央处理单元(CPU)能力和足够的随机存取存储器(RAM)。接下来,我们来讨论一下遵循这本书的最低硬件规格。 ## 硬件:笔记本电脑或个人电脑的最低规格 要安装、配置和练习本书中的所有练习和 Python 网络自动化实验室,您的计算机必须满足或超过表 1-2 中列出的最低规格。由于大多数网络工程师在工作中使用 Windows 10,我们也将使用该操作系统作为构建 IDE 实验室环境的基础操作系统。不幸的是,如果你使用的是 macOS 或 Linux,那么你只能靠自己去寻找合适的软件和兼容性。如果你有一台预装了 Windows 10 的功能强大的笔记本电脑或 PC,你就处于阅读这本书的绝佳位置,但如果你的系统仍在运行 Windows 8.1 或 7,强烈建议你升级到最新的 Windows 10。如果你有一台相当强大的台式电脑,有优化的 CPU 和系统冷却,它会比笔记本电脑运行得更好。然而,如果你的笔记本电脑是最新最好的,我会推荐你使用笔记本电脑的移动性和较低的电费。 由于本书的大部分内容侧重于在单个 Windows 10 系统上安装软件、创建实践实验室和运行网络自动化实验室,请确保您的系统满足表 1-2 中指定的最低要求。否则,您将会遇到系统性能问题,例如实验室中的系统响应时间缓慢。 表 1-2。 笔记本电脑或个人电脑:最低规格 | 电脑组件 | 最低规格 | | --- | --- | | **CPU** | 英特尔:CPU i7 第三代(64 位)或更高版本 AMD:锐龙 3600 以上 | | ram | DDR4 为 16GB 或更多(DDR3 为 24GB) | | **固态硬碟** | 240GB 或更多(15%或可用空间用于系统分页) | | OS | Microsoft Windows 10 (64 位)或更新版本 | 图 1-6 展示了我用来写这本书的桌面系统。 ![img/492721_1_En_1_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_1_Fig6_HTML.jpg) 图 1-6。 我的系统规格 如果您的 CPU 与英特尔 i7-3770 相当或更好,如图 1-6 所示,并且拥有 16GB 以上的 DDR4(或 24GB DDR3) RAM,所有实验室都应该可以顺利运行。此外,建议使用 SSD 而不是 HDD,因为机械硬件是可能成为系统瓶颈的一个组件。本书旨在让您能够创建一个动手实验室,在这里您可以从一台 PC/笔记本电脑上学习 Python、虚拟化、Linux、网络和网络自动化。正如我们将在后面解释的那样,本书中的实验对于 Python 网络自动化学习以及学习各种认证(如思科 CCNA/CCNP/CCIE、Checkpoint、Palo Alto 和 Juniper Junos)的学生非常有用,因为 GNS3 支持各种供应商操作系统。 ## 软件需求 您将学习如何安装和集成各种技术,同时在一台 PC/笔记本电脑上构建高度灵活和集成的实验室。表 1-3 包含所有软件并提供下载链接。你可以在开始第二章之前下载所有的软件,或者按照这本书的说明下载不同的软件。 请注意,并不是这里使用的所有软件都是免费软件或开源软件,您可能必须使用演示软件或付费软件。例如,VMware 的 Workstation 15/16 Pro 将在最初的 30 天内以演示模式运行,然后您必须购买有效的密钥。另一个例子是思科建模实验室-个人版(CML-PE),每年的订阅费为 199 美元。对于我们的实验,您只需要表 1-3 中列出的三个文件。这里使用的所有其他软件要么是免费软件,要么是开源软件。 表 1-3。 软件和下载链接 | 所需软件 | 使用 | 下载链接 | | --- | --- | --- | | VMware Workstation Pro 15 或 16(演示/付费) | 桌面虚拟化 | [`https://my.vmware.com/web/vmware/downloads/info/slug/desktop_end_user_computing/vmware_workstation_pro/15_0`](https://my.vmware.com/web/vmware/downloads/info/slug/desktop_end_user_computing/vmware_workstation_pro/15_0)`or`[`https://my.vmware.com/web/vmware/downloads/info/slug/desktop_end_user_computing/vmware_workstation_pro/16_0`](https://my.vmware.com/web/vmware/downloads/info/slug/desktop_end_user_computing/vmware_workstation_pro/16_0) | | GNS3-2-2-14 一体化或更高版本(适用于 Windows) | 网络设备仿真器 | [`https://github.com/GNS3/gns3-gui/releases`](https://github.com/GNS3/gns3-gui/releases)`or`[`https://www.gns3.com/software/download`](https://www.gns3.com/software/download) | | GNS3。VM . VMware . workstation . zip | VMware 工作站 GNS3 虚拟机 ova 映像 | [`https://github.com/GNS3/gns3-gui/releases`](https://github.com/GNS3/gns3-gui/releases) | | IOSv-L3-15.6(2)T.qcow2 或更新版本(付费) | Cisco CML L3 映像 | [`https://learningnetworkstore.cisco.com/cisco-modeling-labs-personal/cisco-cml-personal`](https://learningnetworkstore.cisco.com/cisco-modeling-labs-personal/cisco-cml-personal) | | IOSv_startup_config.img(已付费) | Cisco CML L3 引导映像 | [`https://learningnetworkstore.cisco.com/cisco-modeling-labs-personal/cisco-cml-personal`](https://learningnetworkstore.cisco.com/cisco-modeling-labs-personal/cisco-cml-personal) | | IOSvL215.2.4055.qcow2 或更新版本(付费) | 思科 CML L2 映像 | [`https://learningnetworkstore.cisco.com/cisco-modeling-labs-personal/cisco-cml-personal`](https://learningnetworkstore.cisco.com/cisco-modeling-labs-personal/cisco-cml-personal) | | python-3.8.5.amd64.exe 或更新版本(适用于 Windows) | Python 3.8.5 或更高版本 | [`https://www.python.org/ftp/python/`](https://www.python.org/ftp/python/) | | 记事本++ v.7.8.5 (64 位) | Windows 文本编辑器 | [`https://notepad-plus-plus.org/downloads/`](https://notepad-plus-plus.org/downloads/) | | Putty.exe(64 位)(用于 Windows) | SSH/Telnet 客户端 | [`https://www.putty.org/`](https://www.putty.org/)`or`[`https://the.earth.li/~sgtatham/putty/0.74/w64/`](https://the.earth.li/%257Esgtatham/putty/0.74/w64/) | | Ubuntu-20.04-live-server-amd64 . iso(952.1 MB) | Ubuntu 服务器映像-可启动 | [`https://linuxclub.cs.byu.edu/downloads/isos/ubuntu/`](https://linuxclub.cs.byu.edu/downloads/isos/ubuntu/)`or`[`https://www.ubuntu.com/download/server`](https://www.ubuntu.com/download/server) | | centos-7-x86 _ 64-DVD-1804 . iso(4.16 GB) | CentOS 服务器映像,可引导 | [`https://www.centos.org/download/`](https://www.centos.org/download/)`or`[`http://centos.mirror.omnilance.com/`](http://centos.mirror.omnilance.com/) | | c 3725-adventerprisek 9-mz . 124-15。T14.bin 或者 c 3745-adventerprisek 9-mz . 124-15。T14d.bin | GNS3 集成的 Cisco IOS 映像 | 来自旧的二手思科设备或者通过谷歌搜索 | | Unpack.exe | Cisco image unpacker 0.1 二进制 Windows 版 | [`http://downloads.sourceforge.net/gns-3/Unpack-0.1_win.zip?download`](http://downloads.sourceforge.net/gns-3/Unpack-0.1_win.zip%253Fdownload) | | WinMD5Free | Windows MD5 检查器 | [`https://www.winmd5.com/`](https://www.winmd5.com/) | | 温斯 CP | `Se`针对 Windows-Linux 的固化文件传输 | [`https://winscp.net/eng/download.php`](https://winscp.net/eng/download.php) | | FileZilla 客户端(64 位,用于 Windows) | 用于 Windows-Linux 的安全文件传输 | [`https://filezilla-project.org/`](https://filezilla-project.org/) | ## 使用 GNS3 构建网络自动化开发环境 网络自动化开发环境是用于网络应用开发的 IDE 实验室。在本书中,为了方便起见,我们将称这个环境为*实验室*或*网络实验室*,而不是*集成开发环境*或*网络自动化开发环境*。如前所述,为 Python 网络自动化创建学习实验室有不同的方法。大约 15 年前,当网络学生学习思科路由和交换时,许多学生混合使用基于硬件的实验室、思科的网络操作系统模拟器 Packet Tracer 和开源模拟器 Dynamips。随后推出了 GNS3,这是一款具有 GUI 界面的 Dynamips,易于使用。Dynamips 是一个很好的用于学习的网络设备模拟器,但是它缺少 GUI,对于初学者来说有点困难。GNS3 是一个提供图形用户界面(GUI)的程序,可以轻松管理仿真实验室设备。 在早期,GNS3 有很多错误,并不总是工作良好。随着更先进的 CPU 架构的发展、RAM 容量的增加以及固态硬盘(SSD)的推出,从笔记本电脑或 PC 上的应用模拟供应商网络设备变得更加舒适和易于访问。PC 硬件组件的降价和更多的开源网络仿真器使得学习网络变得非常容易。GNS3 的最新版本没有许多预先存在的错误,可以运行非常稳定的实验室环境。GNS3 已经发展到支持较旧的 Cisco IOS,并集成了 Cisco IOU 和 CML(VIRL)的 L2 和 L3 IOS。您可以运行两个 L2 交换机,并且可以在虚拟化实验室环境中模拟和运行 L3 路由器。尽管研究任何技术的最佳方式是使用合适的设备或相同的虚拟基础架构,但这一选项可能并不适用于所有人。如果钱和时间都有,最好用合适的设备学习。用仿真器学习总是比用模拟器好,如果你能负担得起用适当的设备运行真正的实验室就更好了。表 1-4 提供了网络实验室使用的应用列表,帮助您了解网络模拟器和仿真器在网络研究中的历史。 表 1-4。 用于网络研究的网络仿真/模拟程序 | 应用 | 模拟器/仿真器 | 免费/开源/专有 | 利弊 | | --- | --- | --- | --- | | **思科数据包****示踪器** | 模拟器 | 所有人 | **优点:**对网络学院的学生免费非常容易使用(适合初学者)对学习基本概念很有用在 torrent 网站或免费文件共享网站上随时可用(下载和使用风险自担)思科认证学习工具 | | cons:t1]没有什么接近真正的思科 IOS 没有什么接近真正的思科硬件 | | **活力** | 仿真器 | 开放源码(仍然需要提取的 Cisco IOS 映像) | **优点:**自由的相当易于使用(适合中级用户)对学习基本概念很有用支持旧的路由器平台非常通用与虚拟机轻松集成易于与其他硬件集成比 GNS3 更少的错误支持 Windows 操作系统和 Linux 操作系统思科认证的优秀学习/工作工具 | | cons:t1]你必须找到你自己的 IOS 版本仅支持较旧的 IOS 路由器(7200 路由器除外)有限的交换能力仅通过开源论坛提供支持 | | **GNS3** | 仿真器 | 开放源码(仍然需要提取的 Cisco IOS 映像) | **优点:**自由的非常容易使用(适合初学者)对学习基本概念很有用支持旧的路由器平台非常通用与虚拟机轻松集成易于与其他硬件集成支持 Windows 操作系统和 Linux 操作系统思科认证的完美学习/工作工具 | | cons:t1]找到您自己的 IOS 副本仅支持较旧的 IOS 路由器(7200 路由器除外)有限的交换能力很多虫子许多性能问题仅通过开源论坛提供支持 | | **思科借据** | 仿真器 | 所有人 | **优点:**相当易于使用(适合中级用户)支持更新或最新的 IOS 出色的 L2/L3 交换机仿真可以在大多数 WIN/Linux 主机上运行在种子网站或免费文件共享网站上随时可用思科认证的优秀学习/工作工具 | | cons:t1]所有人需要相对强大的主机思科不提供支持。找到您的副本并使用您自己的风险 | | **思科 CML****(病毒)** | 仿真器 | 所有人 | **优点:**相当容易使用支持更新或最新的 IOS 出色的 L2/L3 交换机仿真可以在大多数 WIN/Linux 主机上运行思科认证的优秀学习/工作工具如果订购付费,思科将提供支持在种子网站或免费文件共享网站上随时可用 | | cons:t1]所有人需要相对强大的主机 | | **UNL Lite****(EVE-NG)** | 仿真器 | 所有人 | **优点:**支持更新或最新的 IOS 出色的 L2/L3 交换机仿真可以在大多数 WIN/Linux 主机上运行思科认证的优秀学习/工作工具 | | cons:t1]需要相对强大的主机 | | **流浪者** |   | 开放源码 | **优点:**自由的用于在单个工作流中构建和管理虚拟机环境的工具易于使用的工作流程专注于自动化缩短开发环境设置时间增加生产平价 | | cons:t1]需要相对强大的主机 | 过去十年中推出的其他仿真器包括统一网络实验室的 UNL Lite(更名为 EVE-NG)、思科的 IOU(Unix 上的 IOS)和思科的 CML(思科建模实验室,老 VIRL)。使用 Cisco Network Academy 帐户学习 Cisco 认证时,可以使用 Cisco Packet Trace。该软件仅用于思科技术支持中心(TAC)与思科 IOU 的实验。Cisco TAC 以外的公司或个人不能使用它。网上有许多关于如何使用以前软件的书籍和免费指南,但在本书中,运行在 VMware Workstation Pro 上的 GNS3 VM,与 Cisco IOS 和 CML 映像集成,将为我们提供 Python 网络自动化实验室的仿真网络基础架构。 对于这本书来说,理想的实验室是为更少的功耗而优化的实验室,可以随时随地方便地操作。所以,这本书按照从容易到中级的顺序,精心布置了题目。此外,只要您使用本书中使用的相同或更新版本的软件,大多数实验设置应该没有软件兼容性问题,并且所有实验都应该是可重复的。 假设你想用这本书的内容进行群体训练,只想在 Python 实验室教学生。在这种情况下,您可以将 Windows 10 作为虚拟机安装在 ESXi 6.5 上(图 1-7 ),安装所有软件,完成集成,然后克隆原始虚拟机,并提供对指定虚拟机的 RDP 访问。 ![img/492721_1_En_1_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_1_Fig7_HTML.jpg) 图 1-7。 在 ESXi 6.5 上运行网络自动化实验室进行小组培训的示例 ## 下载补充指南和源代码 在阅读第二章之前,下载所有预安装指南和源代码。 补充安装前指南下载: * 作者网址: [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) `Source`代码下载: * 作者网址: [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) ## 摘要 `We`从总体上讨论了 IT 网络自动化,并对三个主要的 IT 领域组进行了快速对比分析。我们确定了网络工程师团队在开始为网络自动化应用开发编写 Python 代码时必须关注的领域。最后,我们讨论了开始网络自动化之旅的最低硬件和软件要求。您可能已经注意到,硬件需求中没有提到网络硬件需求;一切都将基于软件,甚至是路由器和交换机。 ## 故事时间 1:超级速度鞋厂 ![img/492721_1_En_1_Figb_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_1_Figb_HTML.jpg) 2016 年,围绕机器人和自动化在制鞋行业取代人类劳动力的宣传非常热烈。著名的鞋和运动服装制造商,德国的阿迪达斯,宣布了一个新的机器人鞋厂;在公告期间,有人预测,由于这种类型的工厂,东南亚 900 万工人中高达 90%的人可能面临失业。然而,在 2020 年,阿迪达斯宣布将停止其在德国安斯巴赫和美国工厂的两家机器人“速度工厂”的生产。机器人鞋生产线只能生产有限数量的模型,这些模型主要由具有针织鞋面鞋底的跑鞋组成;机器人不能生产橡胶底的皮鞋。旧机器已被转移,目前由阿迪达斯在越南和中国的供应商使用。 有人说机器将接管世界,迟早会夺走我们所有的工作,但这个例子强调了更仔细规划和引入工作场所自动化的必要性。每个行业都在通过反复试验来学习自动化的力量。 这一失败也让我们回到了 20 世纪 90 年代初,当时推出了自动取款机(ATM)。在这种情况下,美国的一些研究发现,许多著名经济学家和学者做出的经济预测就像当地的芋头算命师一样准确。因此,如果我们做好了充分的准备并接受这些变化,我们就不应该害怕 IT 行业的自动化或人工智能。虽然支持贵公司的 IT 生态系统并为自己的工作感到自豪对所有 IT 工程师来说都非常重要,但我们必须如实说明我们能做什么和我们知道什么。IT 工程师并不了解他们所支持的所有 IT 技术的方方面面。因此,他们不应该试图掩盖他们无法完成特定任务的事实。我们应该随时准备发现新事物,获得额外的技能来推进我们的职业生涯。 # 二、在 Windows 上学习 Python 基础知识 Windows 是私人和企业用户最常用的最终用户操作系统。越来越多的人将在 Windows PC 上开始他们的 Python 之旅,而不是在 Linux 或 macOS 上,并且当你开始在 Windows 操作系统上用 Python 编码时,Python 的障碍会更低。本章包含将选择性 Python 概念与一般编程概念联系起来的 Python 实践练习,一些练习将参考真实场景。本章将作为后面章节中 Python 网络自动化脚本实验室的入门。遵循编码传统,您将从强制性的“Hello World!”开始学习 Python 程序。在开始练习之前,将指导您设置一个简单的 Windows Python 环境,并向您介绍一些基本的 Python 编码规则。您将学习最基本的 Python 语法和概念,构建更强大的基础模块,并获得 Python 网络自动化实验室所需的基本 Python 技能。 ![img/492721_1_En_2_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Figa_HTML.jpg) ![img/492721_1_En_2_Figb_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Figb_HTML.gif)要在 Windows 操作系统(OS)上学习和重温 Python 的基本概念,请在您的笔记本电脑或 PC 上安装 Python 3,并遵循本章中的练习。从 GitHub 下载`CH02_Pre_Task_Install_Python3_on_WIN10_NotepadPP.pdf`。 URL: [`https://github.com/pynetauto/installation_guide`](https://github.com/pynetauto/installation_guide) 这里我就分享一下自己的经历。我第一次开始学习 Python 是在 2013 年,希望能将它应用到我的工作中。在这第一次尝试中,仅仅是尝试学习简单的 Python 语法就花了太长时间,而且感觉很繁琐,毫无意义,所以两个月后我就放弃了。2015 年,我又尝试了一次 Python 语法和概念。我花了三个月的时间学习了基本的 Python 语法和概念,但是无法将 Python 语法和概念与我的工作联系起来,所以我再次放弃了。快进到 2017 年,我无休止地寻找着我所缺少的东西。我终于意识到,学习和掌握一门编程语言本身是一种无用的练习,除非你有一套目标和一个清晰的路线图。 简单地说,我的目标是作为一名网络工程师学习和使用 Python 来自动化重复性的任务。结论是学习 Python 只是旅程的一部分。我必须涵盖更多的部分,例如,提高 Linux 系统管理技能,掌握正则表达式,学习使用各种 Python 模块作为内置和外部模块,以及任何特别的东西来快速调整我的代码。 开始第一次编程语言学习之旅需要我们大多数人的强烈动机和个人奉献精神。持续的学习需要耐心和坚强的意志力。对于任何学习,一旦你完成了旅程的第一部分,第二部分就在等着你,在你完成第二部分之后,第三部分就在等着你。学习没有明确的终点,你将陷入这个恶性循环,因为你将不得不在旅程的不同部分来回。要想让自己的 Python 学习曲线更舒服,首先花点时间想想自己的动机,为什么要学 Python,想用在什么地方;当你懈怠的时候,把它作为提醒,让你重回正轨。此外,设定正确的期望值,用足够的时间深入吸收 Python 语法和概念,并探索适合您的用例的各种 Python 模块。用 Python 写代码和用其他编程语言写代码没什么区别。还有,学习 Python 语法就像学习一门非母语的语言,要花很长时间才能完成基础,进入下一个层次。像任何其他学习一样,这是你内心的一场持续斗争,但是一个精心计划的学习策略可以让你少一些痛苦。 现在让我们来看看你与电脑互动的不同方式;有三种主要的方式可以让你和电脑互动并指导电脑执行任务。首先,你坐在物理计算机或直接连接到系统的远程控制台机器前,以一对一或一对 n 的方式发出实时指令。其次,您可以使用文本编辑器或 IDE 编写一段代码,并手动执行该代码。这种方法被称为*半自动*。第三,你可以编写代码,安排它在特定的时间运行,系统(计算机)自动执行你的代码,无需人工干预。这种方法被称为*全自动*或*全自动*。众所周知,Python 是一种不需要预编译的交互式编程语言。交互式编程语言当场解释源代码,并在代码需要执行时向计算机发出指令。 如第一章所述,虽然这是一本关于 Python 网络自动化的书,但这本书将 Python 知识和技能视为实现 Python 网络自动化所需的许多技能集的一部分。因此,这本书并不仅仅关注 Python 语法和概念,而是旨在拓宽您在网络自动化之旅中所需的各种 IT 技术的视角。本书将尝试向您展示几种技术,使您成为一名全面的技术专家,能够使用 Python 编写代码、管理 Linux、开发应用,并为工作构建概念验证(POC)网络实验室。假设您只想学习基本的 Python 语法。在这种情况下,购买一本很好的 Python 基础知识书籍会更合适,这本书可以在亚马逊或当地书店买到。 您的 Python 知识和经验会因里程而异。但是在这一章中,我将假设你是第一次或新手 Python 程序员,所以这一章将涵盖一些基本的 Python 语法和概念,以便执行本书要求的所有任务。这本书逐步线性地构建你的 Python 网络自动化技能集。作为一名读者,我们鼓励你在键盘上完成本章介绍的每个练习。在进入下一章之前,您必须键入代码并完成本章中的所有练习。 不像其他的书,这本书会先给你各种练习,然后在本章和整本书中解释。有太多的书过于详细地解释了这些概念。这本书是给实干家看的,不是给概念思想家看的。一些练习将包含作为嵌入注释的解释,但在大多数情况下,解释将跟随每个练习,以进一步帮助您的理解。在每个 Python 概念里程碑的末尾,您会发现一个简短的概念总结,作为对您所学内容的提醒。最后,这本书不会给你提供琐碎的测验、不合理的问题或荒谬的挑战,让你陷入大脑昏厥。 ## “你好,世界!”和 print()函数 学会打印强制性的“你好,世界!”并理解交互模式和脚本模式之间的区别。 Hint 带空格的是 Python 提示符,表示您正在交互模式下工作。 如果你想在 Windows 10 上交互式地学习 Python,方法很少,但三个主要方法在 Python shell 中,在命令提示符下,在 Windows PowerShell 中(图 2-1 )。在交互模式下,当你打开一个 Python shell 时,它会用`>>>`(三个右括号和一个空格)和一个闪烁的光标来欢迎你。Python 告诉你它已经准备好接受你的交互输入了。当你在这种模式下编写代码时,你是在交互模式下编写代码,它将你的代码保存在计算机的随机存储器中,然后被 Python 即时解释。在本章展示的例子中,您将使用一个简单的`print()`函数打印出一个必须的“Hello World!”语句开始您的 Python 之旅。用一组单引号或双引号将一些字符串括在一组圆括号中,然后按键盘上的 Enter 键。Python 解释器立即解释代码,并将输出打印到计算机屏幕上。Python 和 Ruby、Perl 一样,是一种解释型编程语言。 ![img/492721_1_En_2_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Fig1_HTML.jpg) 图 2-1。 Windows 10 上的 Python 交互式编码 打开提示方法之一,键入强制的`print('Hello World!')`或`print("Hello World!")`语句。你学会了三件事。第一,你已经学会了如何使用`print()`功能;第二,你已经知道 Python 使用一组单引号(`' '`)或者一组双引号(`" "`)来包装字符串。本章将通过各种示例讨论`print()`功能和引号的使用。第三,本练习向您介绍了作为验证工具的强制`print('Hello World')`语句。 无论您是 Python 编程语言的新老用户,阅读一下“Hello World!”的起源都是值得的以及它的用例。以下是两个网址,供你访问和了解著名的“Hello World!”程序。对于我们在本书中的使用,您将使用它来验证 Python 作为一个程序是否正常运行,并且可以在运行`print()`函数时在控制台屏幕上打印字符串。 ![img/492721_1_En_2_Figc_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Figc_HTML.gif)了解更多关于“你好,世界!”程序,请参见以下内容: URL: [`https://en.wikipedia.org/wiki/%22Hello,_World!%22_program`](https://en.wikipedia.org/wiki/%252522Hello,_World%2521%252522_program) URL: [`https://www.youtube.com/watch?v=ycl1VL0q1rs`](https://www.youtube.com/watch%253Fv%253Dycl1VL0q1rs) 在 Python 3 中,`print()`是一个内置函数,也是一个标准函数,用于在用户的计算机屏幕上显示处理后的数据。由于 Python 2.7.18 版本标志着 2.x 版本支持的结束,并于 2020 年 1 月 1 日到达生命的尽头,所以本书中的所有 Python 代码都是用 Python 3.6.4 编写的。不要为 Python 2.x 而烦恼,因为一旦你牢牢掌握了 Python 3.x,你就会意识到这两者之间没有太多的区别。一个公平的类比是 Windows 8 和 Windows 10 之间的差异;如果你知道如何使用 Windows 10,那么你就可以轻松使用 Windows 8。 你也可以在文本编辑器中编写 Python 代码,并在 Windows 10 上运行脚本之前将其保存为`.py`文件格式。当你在文本编辑器中写代码时,我们称之为*写脚本*或在*脚本模式下工作*。当你在 Windows 10 PC 上安装 Python 时,Python 提供了一个内置的文本编辑器(图 2-2 ),或者你可以使用自己选择的文本编辑器来编写你的代码并保存为`.py`文件。Python 代码是独立于平台的,因此在 Windows 操作系统(OS)上编写的代码可以在 macOS 或 Linux OS 上运行,反之亦然。在本章中,我希望你打开 Python IDLE 和内置的文本编辑器,在键盘上练习每一行代码,这样你就可以体验用 Python 写代码的感觉了。在功能更丰富的集成开发环境(IDE)中编码也是可能的,但是我们将在本章后面简要讨论这一点。现在,尝试使用 Python 解释器和简单的文本编辑器环境编写代码。您不希望在这个阶段担心使用哪个 IDE。目前,掌握如何使用 Python shell 和内置文本编辑器已经足够了。要实用,不要时尚! 打开 Python 内置的文本编辑器,编写下面一行代码,在`Python39`文件夹中另存为`welcome_pynetauto.py`。然后转到运行并选择运行模块 F5。 *welcome _ pynetato . py* ```py print("Welcome to Python Network Automation") print("Welcome to Python Network Automation") ``` ![img/492721_1_En_2_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Fig2_HTML.jpg) 图 2-2。 在 Windows 的 Python 内置文本编辑器中编码 完成本章的所有练习后,你将更加熟悉基本但核心的 Python 概念;在进入本书后半部分的后续章节之前,这种理解是必须的。如果使用的是 Linux 或 macOS,可以打开一个终端窗口,输入`python`或`python3`启动交互模式。我们将在本书的后面讨论 Linux 操作系统中的编码。 ## 准备 Python 练习 这本书的绝大多数目标读者是 Windows 用户。要开始本章的练习,你应该已经按照`CH02_Pre_Task_Install_Python3_on_WIN10_NotepadPP.pdf`的安装指南在你的 Windows 10 主机上安装了最新版本的 Python 3。根据 2019 年的统计,Windows 用户仍然以 20 比 1 的比例超过 Linux 用户。因此,在 Windows 环境下开始您的 Python 之旅是理想的,然后您可以过渡到 Linux。由于大多数支持 Python 的企业服务器运行在众多 Linux 操作系统风格中的一种上,这就为企业工程师掌握 Linux 技术提供了足够的理由。在后面的章节中,您将学习 Linux 基础知识,并且您将在 Linux 环境中执行任务,因此我们将从在 Windows 上使用 Python 开始,然后学习在 Linux 上使用 Python。如果你一直是 Windows 用户,你必须开始使用 Linux 操作系统,以便在大多数 IT 技术工作中领先于其他人。如果您已经熟悉 Linux,那么您将乘坐商务舱进行 Python 网络自动化之旅。 在本章中,你将完成各种练习,并使用解释和概念总结来回顾你从这些练习中学到了什么。为了让你开始 Python 编码,你将首先学习四个必不可少的 Python 概念,看一些例子,然后你将尝试练习。 * Python 数据类型 * 缩进和代码块 * 评论 * 基本 Python 命名约定 您可以在 Linux 或 macOS 上练习本章介绍的所有练习,只需对目录位置稍加修改。当你在 Python 内置的 IDLE 或 Windows 命令提示符或 Windows PowerShell 中打开 Python 并输入`python`,Python 就会运行并以友好的交互式 Python 提示符向你问候。请密切注意;您将看到它有三个大于号,后跟一个空格。 >>> 当您看到三个大于号和一个空格时,Python 会告诉您可以输入下一行代码。适应这个符号,因为你会花很多时间盯着它。 ### 了解数据类型 在本节中,您将了解 Python 中使用的不同数据类型:数字、序列、映射、集合和`None`。 Hint 试着考虑 Python 数据类型,以便在将来节省时间。 Python 数据类型是一组具有相似特征的值。数据类型可以属于图 2-3 所示的一组。我们将使用 Python 的内置函数`type()`研究每种数据类型。 ![img/492721_1_En_2_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Fig3_HTML.jpg) 图 2-3。 Python 数据类型 在 Python 中,一切都被视为一个对象,并根据对象的特征分类为不同的数据或数据类型。首先,让我们快速了解一下 Python 的数据类型。这本书试图在开发各种网络自动化应用时,建立在你的基础到中级 Python 代码的知识之上。因此,您不必深入了解所有的数据类型,但是您必须了解最常用的数据类型;我们将只讨论通读这本书所需的基础知识。 您将使用 Python 的内置函数`type()`来了解 Python 如何对不同的数据对象进行分类。打开你的 Python shell,在`>>>`后面输入文本和空格;然后按回车键。 | # | 锻炼 | 说明 | | --- | --- | --- | | ① | `>>> type (3)``` | 整数是数字的子类。1 是整数的一个例子。带有+或-符号的整数,如-3、0、5,都是整数。它们用于计算的数字数据中。 | | ② | `>>> type (True)````>>> type (False)``` | 布尔值(`True`或`False`)是整数的子类。布尔运算测试`True`或`False`的一些条件。常数 1 代表`True`,0 代表`False`。 | | ③ | `>>> type (1.0)``` | float 是数字的子类,也称为*浮点*。在这个例子中,1.0 是一个浮点数的例子。浮点数是带分数或小数点的数字。 | | ④ | `>>> type (1 + 2j)``` | 复数是实数和虚数的组合。你可以把它想象成便于计算的数字数据,经常用于科学计算。 | | ⑤号 | `>>> type ('123')````>>> type('word')``` | 字符串是 sequence 的子类,是可变的,是字母或字符的有序序列。序列是由从 0 到`n`的整数索引的项目的有序集合。任何用双引号或单引号括起来的数据类型,例如`'123'`或`"word"`,在 Python 中都是一个字符串。 | | ⑥ | `>>> type ([1, 2, 3])``` | 列表是任何数据类型的值序列的子类。列表中的值是项,是可变的,用 0 到`n`的整数索引,有序。该列表使用一组方括号`[ ]`来表示订单中的项目。 | | ① | `>>> type ((3, 2, 1))``` | 元组是任何数据类型的值的序列,并且由整数索引。它是一种不可变的数据类型,用圆括号`( )`列出元素,用逗号分隔。列表和元组的区别在于它们的不变性。 | | ⑧ | `>>> type ({1: 'apple', 2: 'banana'})``` | 字典是一种无序的映射数据类型,通过构造`key: value`对,用逗号分隔它们,并用大括号`{ }`将它们括起来来创建。比如`'a':'apple'`中的`a`叫做*键*,冒号后面的`apple`叫做*值*。在字典中,忽略元素的顺序,通过调用一个键对值进行分页。 | | ⑵ | `>>> type ({1, 2, 3, 3, 2, 1})````>>> {1, 2, 3, 3, 2, 1}``{1, 2, 3}` | 集合是没有重复条目的任何数据类型的值的无序集合。`set`数据类型也是不可变的。如示例所示,集合只允许相同值的单个条目。 | | ⑩ | `>>> type (None)``` | `None`是一种特殊的单值数据类型。它是一个常数,表示没有值。如果你把`None`赋给一个变量,就意味着这个变量是空的。 | | ⑪ | `>>> int(7.24)``7``>>> float(3.00)``3.0``>>> str("365")``'365'``>>> bool(1)``True` | 显式类型转换可以指定精确的数据类型。Python 在自动检测数据类型方面做得非常出色,但是有时,有些数据需要修正以避免混淆。在 Python 代码中处理不同的数据类型时,经常会用到数据类型构造函数,如`int()`、`float()`、`str()`和`bool()`。 | ![img/492721_1_En_2_Figd_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Figd_HTML.gif)我们可以在 [Python 上参考每种数据类型更详细的解释。org](http://python.org) 文档网站。 URL: [`https://docs.python.org/3/library/stdtypes.html`](https://docs.python.org/3/library/stdtypes.html) ### 缩进和代码块 C、C++和 Java 等大多数编程语言都使用大括号`{ }`来构造代码。Python 使用缩进来定义一个代码块,赋予其独特的代码结构;代码块是程序或脚本中的一组语句,由至少一条语句和编程块的声明组成。在 Python 中,一个代码块以缩进开始,以第一个未缩进的行结束;示例包括函数体、循环和类。在本节中,您将学习使用缩进或空白作为代码块。 Hint Python 使用四个空格来区分代码块。 | ① | `with open("C:\\Python39\\ip_adds.txt", "r") as f:`□□□□ `for line in f:`□□□□□□□ `print(line)``# Output: 10.20.30.1``172.168.1.254``192.168.0.2``10.20.34.25` | | ② | `with open("C:\\Python39\\ip_adds.txt", "r") as f:`□□ `for line in f:`□□□□ `print(line, end="")``# Output: 10.20.30.1``172.168.1.254``192.168.0.2``10.20.34.25` | #### 说明 | ① | Python 使用空格对语句进行分组;这也被称为*缩进*。第一行代码通常没有空格;当语句以冒号(`:`)结束时,下一行需要缩进以表明它是组的一部分。示例①是打开带有 IP 地址的文件并读取 IP 地址的代码。四种缩进是 PEP80 建议,也是用 Python 编码时最广泛使用的缩进约定。该示例显示第二行有四个空格,第三行有八个空格。输出仅用于演示,但是您可以在`C:\Python39\`下创建一个名为`ip_adds.txt`的文本文件,输入几个 IP 地址,并测试 Python 解释器中显示的代码。 | | ② | 同样的代码仍然可以使用两个空格进行缩进。只要你的间距是一致的,你可以为你的代码块使用任何间距;然而,四倍间距是正常的。在 Google,Python 编码标准使用两个空格。在最后一行代码中,我添加了`end=""`来消除从文件中读取信息时产生的空白。使用`end="`、`end=" "`、`end = "/"`、`end=":"`等等,您可以操纵如何在输出中处理不想要的空白。 | ### 评论 注释是不影响程序流程的代码的一部分,它提供了对应用重要部分的描述。注释可以解释某些功能的用途,或者解释复杂的代码片段来帮助读者。Python 在处理注释时使用了一个`#`符号,后续字符被注释掉,对程序没有影响。要添加多行注释,可以在多行之间使用三单引号(`''' '''`)或三双引号(`""" """`)。Python 注释的另一个用途是在开发应用的同时,使部分代码不活动。 在本节中,您将学习如何向代码中添加注释。 Hint 代码读的次数比写的次数多。良好的评论习惯可以节省解码时的时间。 | ① | `# This code can SSH into multiple devices & download IOS from a TFTP server` | |   | `# and upgrades old IOS to the latest version.` | |   | `# Please... [skip]` | | ② | `# Ask new driver's age` `.` | |   | `age = 17` | |   | `if age > = 18:` | |   | `print('You are old enough to drive a car.')` | |   | `#elif age > 80:` | |   | `# print('You are too old to drive a car!') else:` | |   | `print('You are too young to drive a car.')` | | ③ | `"""` | |   | `This code can SSH into multiple devices and download IOS from a TFTP server` | |   | `And upgrades old IOS to the latest version. Please check MD5 values of the new` | |   | `IOS image before uploading it to the router flash.` | |   | `"""` | #### 说明 | ① | 通过在每一行的开头输入`#`散列(尖锐)符号来添加注释。Python 忽略了`#`之后的一切。注释用于解释和阐明源代码的含义,或者为示例①中使用的源代码提供指导。 | | ② | 或者在应用开发过程中临时禁用部分代码,如例②所示。 | | ③ | 对于多行(建议是三行或更多行),使用三重引号。 | ### Python 命名约定 在本节中,您将学习与 Python 相关的标准对象命名约定。 Hint 要编写一致且高质量的 Python 代码,您必须采用标准化的命名约定。 和其他编程语言一样,Python 也有一些命名约定要遵循。如果您的 Python 代码将被其他人阅读,那么遵循这些约定是一个最佳实践,以避免以后的混乱和误解。当您命名变量、函数、模块、类等等时,请给出有意义的、合理的和描述性的名称,以避免其他人产生歧义。使用`#`或三重引号在代码中添加额外的注释。表 2-1 概述了新 Python 编码者的推荐命名约定。 表 2-1。 Python 命名约定 | 类型 | 例子 | 约定 | 描述 | | --- | --- | --- | --- | | 可变的 | `x`、`y`、`z`、`var`、`tmp`、`f_name`、`s_name` | 不大写带下划线的一个或多个字母、单词 | 使用小写字母(表示增量)、单词或带下划线分隔符的单词。 | | 功能 | `function my_function` | 无大写单词或带下划线的单词 | 使用小写单词或单词,或带有下划线分隔符的单词。 | | 方法 | `method class_method` | 与功能相同 | 使用小写单词或带有下划线分隔符的单词。 | | 组件 | `module.py network_tools.py` | 与保存为`.py`文件的功能+模块相同 | 将模块保存为`.py`文件时,使用短的小写单词或带下划线分隔符的单词。 | | 包裹 | `package firstpackage` | 所有小写单词或不带下划线的单词 | 使用一个或多个短的小写单词,不带下划线分隔符。 | | 班级 | `Class FirstClass` | 第一个字母大写,不带下划线 | 使用骆驼肠衣;每个单词以大写字母开头,不带下划线分隔符。 | | 常数 | `CONSTANT FIRST_CONSTANT FIRST_TIME_CONSTANT` | 用下划线大写 | 使用大写的单个字母、单词或带有下划线分隔符的单词。 | 在进入下一部分之前,花点时间研究每种类型、示例和约定。如果需要,您也可以稍后回到此表作为参考点。 ## 做 Python 练习 完成所有练习,掌握网络自动化编码所需的基本 Python 语法和概念。 Hint 本章中的所有练习将帮助你更好地理解本书后面介绍的网络自动化代码。您必须了解 Python 基础知识,才能继续您的 Python 网络自动化之旅。 我们鼓励您空闲时打开 Python,并执行本书中给出的所有练习。您首先在 Python IDLE 中键入语法,然后练习后面的解释会帮助您理解练习的主题。本章提供的选择性练习与我第一次学习 Python 时使用的相同。这将使您熟悉 Python 语法和概念。如果你是一个初学 Python 的人,你将有足够的机会开始使用 Python 进行网络自动化编码。您将学习理解 Python 代码所需的必要 Python 技能,为本书后面的章节做准备。我想教你更多关于 Python 语法的知识,但这本书的重点是网络自动化、虚拟化、Linux 和思科网络。因此,我们有选择地选择了相关的练习来涵盖 Python 的基础知识,这样当你读到最后的实验章节时,你会对跟随练习感到舒服。 在本章中,当您键入练习中的第一个字符时,请尝试在一个会话中完成练习的最后一行。即使你第一次尝试时不理解代码,也要第二次重复整个练习。本章中使用的许多例子都是不言自明的,或者后续的解释将阐明每个代码。亚马逊上有大量 Python 入门书籍出售,还有大量免费和付费的在线课程。所以,在尝试了本章的练习之后,如果你觉得你想通过学习更多的 Python 概念和语法来提高,请找一本你选择的书或网站,花更多的时间练习 Python 基础知识。 我们将使用 Python 3 而不是 Python 2,原因很简单;Python 2 版本在 2020 年 1 月 1 日达到了生命的终点。 此外,我鼓励您在基于 Windows 的计算机上完成所有练习,这样您就可以了解 Windows 和 Linux 操作系统之间的细微语法差异,主要涉及文件处理以及这两种操作系统之间的目录结构有何不同。我没有提到 macOS,因为它是 BSD UNIX 的衍生物,和许多 Linux 发行版(distros)一样。在第 5 和 6 章中,你将从最流行的 Linux 发行版、CentOS(红帽衍生版)和 Ubuntu (Debian 衍生版)构建虚拟机。为了更平稳地过渡,您需要熟悉 Linux 操作系统,然后在 Linux 上使用 Python 3。 当您启动 Python IDLE 时,它会用三个大于号(`>>>`)和一个光标(`|`)来欢迎您。如果您在练习中看到三个大于号,您必须将内容输入到 Python IDLE 中。如果你必须保存你的代码并作为一个 Python 文件运行,这个文件的扩展名是以`.py`结尾的,你将不得不按照说明在 Python 的内置文本编辑器或记事本中编写代码。虽然我可以向您介绍各种文本编辑器和 IDE,但是我们在这一章的重点纯粹是 Python 语法和概念,所以花哨的 IDE 或文本编辑器是一个障碍。 是时候启动你的 Python IDLE 了。当您看到提示时,开始键入第一个练习中的代码。 ## 变量和字符串 我们将从变量和字符串开始。 ### 练习 2-1:创建变量并分配各种数据类型 | ① | `>>> x = 1``>>> y = 2``>>> x + y`three`>>> type(x + y)``` | | ② | `>>> x = '1'``>>> y = '2'``>>> x + y`'12'`>>> type(x + y)``` | | ③ | `>>> fruit = 'apple'``>>> applause = 'bravo'``>>> dec1 = 1.0``>>> bool_one = True``>>> bool_0 = False``>>> _nada = None` | #### 说明 | ① | 您创建了变量 x 和 y,并给两个整数赋值。执行简单的加法来检查两个数的和。Python 支持其他算术运算;Python 中的等号(=)是赋值运算符。所以,你必须把`x = 1`读作“x 赋给 1”或者“x 指向 1”或者“标签 x 赋给对象 1。”与其他编程语言不同,在 Python 中,不需要预先声明变量;赋给变量的值决定了变量的类型,在这个例子中,它们都是整数。 | | ② | 我们创造了变量 x 和 y。然后,他们被分配到两个不同的字符串。虽然 1 和 2 是整数数据类型,但我们将这些数字用单引号括起来,这样就自动将它们转换成字符串。因此,对两个字符串执行连接将返回字符串 12,而不是两个数字之和 3。 | | ③ | 你应该给变量取一个简单直观的名字。变量名通常以-z 或 _ 开头。此外,避免首字母大写。变量名必须是连续的,没有空格或分隔符。如果您尝试用数字、算术运算符或特殊字符命名变量,您将会收到各种语法错误。另外,Python 是区分大小写的,字符串是不可变的。您不能覆盖不可变对象的值,但是有一个技巧可以改变字符串中的字符。你将在练习 2-22 中学到这个技巧。 | ### 练习 2-2:创建变量并使用 print()函数打印输出 | ① | `>>> x = 100``>>> y = 200``>>> z = 300``>>> total = x + y + z``>>> print(total) 600` | #### 说明 | ① | 我们已经创建了 x、y 和 z 变量并赋予了不同的值,并创建了另一个名为 *total* 的变量,这是三个变量的总和。我们使用 print(total)在屏幕上打印结果。您通常使用`print()`功能在屏幕上显示输出。从计算机或 Python 的角度来看,`print()`语句对现有的变量值没有影响。 | ### 练习 2-3:使用缩写变量赋值 | ① | `>>> x, y, z = 10, 20, 30``>>> x``10``>>> y``20``>>> z``30``>>> type(x)````>>> x, y, z``(10, 20, 30)` | | ② | `>>> a, b, c = "apple", "banana", "coconut"``>>> a, b, c``('apple', 'banana', 'coconut')` | #### 说明 | ① | 您可以使用逗号(,)作为分隔符,将多个变量赋给单行上的多个值。当您对单个变量进行分页时,它作为一个整数对象返回,但是当使用逗号作为一个组进行分页时,它返回一个元组。 | | ② | 这同样适用于字符串数据类型的对象。你注意到双引号被用来代替单引号了吗?只要在整个代码中保持一致,您就可以同时使用这两种方法。 | ### 练习 2-4:输入字符串、转义符反斜杠(\)和 type()函数 | ① | `>>> 'He said, "You are one of a kind."'``'He said, "You are one of a kind."'` | | ② | `>>> said = "He said, \" You are one of a kind.\""``>>> print(said)``He said, "You are one of a kind."``>>> type(said)``` | #### 说明 | ① | 您可以用一组单引号或双引号来表示一个文本字符串。这个例子在 Python IDLE 上使用了单引号。 | | ② | 本示例在内部双引号(")前使用双引号和两个反斜杠(\)。反斜杠转义(删除)双引号的功能含义,并将它们标记为普通字符。通常的做法是使用一种样式的引号来保持一致并提高代码的可读性。使用`type()`功能检查数据类型。 | 表 2-2 提供了反斜杠用例的例子。在进行下一个练习之前,复习表格。您还可以在 Python 解释器中输入示例,以了解不同示例之间的细微差别。 表 2-2。 反斜杠示例 | \使用 | 例子 | 说明 | | --- | --- | --- | | \\ | `>>> print('\\represents backslash\\')``\represents backslash\` | 反斜杠用于转义前导反斜杠。我们也知道这个过程是否定一个元字符的意义,把一个字符当作一个字面字符。 | | \' | `>>> print('\'Single quotes in single quotes\'')``'Single quotes in single quotes'` | 反斜杠用于转义单引号。 | | \" | `>>> print("\"double quotation marks inside double quotation marks\"")``"double quotation marks inside double quotation marks"` | 反斜杠用于转义双引号。 | | \n | `>>> print('Line 1\nLine 2\nLine 3')``Line 1``Line 2``Line 3` | 与 n 组合\取另一个意思,成为换行符,\n。 | | \r(对于 Linux)\r\n(适用于 Windows) | `>>> print('Line 1\r\nLine 2\r\nLine 3')``Line 1``Line 2``Line 3` | 在 Linux 机器上,为了模拟按 Enter 键,使用了`\r`,但是在 Windows 机器上,您必须使用`\r\n`。 | | \t | `>>> print('No tab\tTab\tTab')``No tab Tab Tab` | Tab 与在物理键盘上按 Tab 键具有相同的效果。 | ### 练习 2-5:确定变量是容器、标签还是指针 | ① | `>>> x = "apple"``>>> y = "apple"``>>> id(x)``2590130366512``>>> id(y)``2590130366512``>>> x == y``True` | #### 说明 | ① | Many Python books teach novice Python learners about variables using a variables-as-containers analogy. In this analogy, variable containers temporarily store objects. To some extent, this seems to be a correct analogy, but not exactly 100 percent correct. Variables are more like tags or pointers pointing at an object (see Figure 2-4). The container analogy does not work. As seen in the previous exercise, x and y have been assigned to the same string (an object), `"apple"` (pointing to the same object). You can use the `id()` function to check the object IDs for validation; x and y have the same ID. Using the comparison operator, you can confirm that x is equal to y, and hence they are both pointing to the same object, `"apple"`.![img/492721_1_En_2_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Fig4_HTML.jpg)图 2-4。变量:容器与标签/指针的类比 | ![img/492721_1_En_2_Fige_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Fige_HTML.gif)以下是一些 Python 变量命名约定的建议: 1. 使用小写的单个字母、单词或多个单词。 2. 如果使用两个以上的单词,为了可读性,请用下划线(_)分隔单词。用数字来区分具有相似对象特征的相同变量,例如`fruit_1= "apple"`和`fruit_2 = "orange"`。 3. 变量名区分大小写。`fruit_1`和`Fruit_1`是两个独立的变量。 4. 避免使用 Python 保留字;这将在后面的练习中讨论。 在下面的例子中,你可以把你定义的一个字符或句子放在双引号或单引号中来练习。另外,检查字符串错误。 ### 练习 2-6:使用字符串索引 | ① | `>>> bread = 'bagel'``>>> bread [0]``'b'``>>> bread [1]``'a'``>>> bread [4]``'l'` | #### 说明 | ① | Python 索引从 0 开始。对于示例中使用的单词*百吉饼*,字母`b`是 0 指数,字母`l`是 4 指数。字符串:b a g e l 指数:0 1 2 3 4 | 在编写 Python 代码时,您会遇到各种 Python 错误消息。如果您提前熟悉了经常遇到的错误,您将很快解决这些错误,并且更加喜欢 Python 编码。在以下示例中,您将有意触发几个 Python 错误,以了解更多相关信息。 ### 练习 2-7:理解变量赋值错误 | ① | `>>> +lucky7 = 777``SyntaxError: can't assign to operator``>>> 7lucky= 777``SyntaxError: invalid syntax``>>> 7_lucky = 777``SyntaxError: invalid decimal literal``>>> lucky7 = 777``>>> lucky7``777` | #### 说明 | ① | 前面的语法错误显示了由不正确的变量命名约定触发的错误。如果在开头使用算术运算符符号,Python 将会引发错误。如果在变量名的开头放置一个数字,Python 将返回一个无效的语法错误。如果您正确地分配变量,您将不会遇到错误消息,如前面的示例所示。错误是任何编码中至关重要的一部分,因为它们可以帮助您纠正编码错误。 | ### 练习 2-8:避免“扫描字符串时出现 SyntaxError: EOL” | ① | `# On Python Shell:``>>> q1 = 'Did you have a wonderful day at work?"``SyntaxError: EOL while scanning` `string literal``# On Window Command-Line or PowerShell:``>>> q1 = 'Did you have a wonderful day at work?"``File "", line 1``q1 = 'Did you have a wonderful day at work? "``^``SyntaxError: EOL while scanning` `string literal``>>> q1 = 'Did you have a wonderful day at work?' # Use correct quotation marks, no error prompted.``>>>` | #### 说明 | ① | 将字符串赋给变量时,不能混合使用单引号和双引号。您将得到如下所示的扫描字符串文字错误。本练习展示了来自 Python shell 和 Windows PowerShell 的相同错误输出。若要更正此错误,请确保在字符串的开头和结尾键入了不正确的引号。 | ### 练习 2-9:避免“名称错误:名称‘变量名称’未定义” | ① | `>>> print(a1)``Traceback (most recent call last):``File "", line 1, in ``print(a1)``NameError: name 'a1' is not defined``>>> a1 = 'Yes, I had a lovely day.'``>>> print(a1)``Yes, I had a lovely day.` | #### 说明 | ① | `NameError`当变量未预定义时发生。为了避免这个错误,首先定义您的变量,并在类似于`print(a1)`的函数中使用它。 | ### 练习 2-10:避免“语法错误:无效语法” | ① | `#On Python 3.9Shell:``>>> for = 'I drove my car for 4 hours before taking the first break.'``SyntaxError: invalid syntax` | | ② | `#On Window Command-Line or PowerShell``>>> for = 'I drove my car for 4 hours before taking the first break.'``File "", line 1``for = 'I drove my car for 4 hours before taking the first break.'``^``SyntaxError: invalid syntax``>>> first_break = 'I drove my car for 4 hours before taking the first break.'``>>>` | #### 说明 | ① | 当您使用 Python 的保留单词列表中的单词时,Python 会返回此错误。为了避免这种错误,请避免使用保留字作为变量名。 | | ② | 它在 Windows 命令行中触发了同样的错误,并且给出了更多的信息。`^`(脱字符号)用于表示潜在的问题区域。Python 告诉你大致在^指向的地方进行修正。为变量取一个更具描述性或更有意义的名称。我将变量名重命名为`first_break`,因此变量名与分配给该变量的字符串在上下文中。 | ### 练习 2-11:避免“类型错误:‘str’对象不可调用” | ① | `>>> help = 'Please help me reach my goal.'``>>> print(help)``Please help me reach my goal.``>>> help (print)``Traceback (most recent call last):``File "", line 1, in help (print)``TypeError: 'str' object is not callable` | #### 说明 | ① | 当您将内置函数名指定为变量名时,您会禁用该函数名,并且它会立即变得不可用。避免使用关键字和内置函数名作为变量名。您可以使用`dir(__builtins__)`命令检查所有内置函数名,这将在后面的示例中讨论。 | ![img/492721_1_En_2_Figf_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Figf_HTML.jpg) ![img/492721_1_En_2_Figg_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Figg_HTML.gif)当 help()正常工作时,帮助信息如下所示: `-----------------------------------------------------------------------` `Python 3.9.1 (tags/v3.9.1:1e5d33e, Dec  7 2020, 17:08:21) [MSC v.1927 64 bit (AMD64)] on win32` `Type "help", "copyright", "credits" or "license()" for more information.` `>>>` `help(print)` `Help on built-in function print in module builtins:` ```py print(...) print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False) Prints the values to a stream, or to sys.stdout by default. Optional keyword arguments: file: a file-like object (stream); defaults to the current sys.stdout. sep: string inserted between values, default a space. end: string appended after the last value, default a newline. flush: whether to forcibly flush the stream. >>> --------------------------------------------------------------------- ``` 您已经回顾了一些与 Python 错误相关的常见语法。在编写和运行代码时,您会遇到各种各样的错误代码。有些错误代码很容易排除,但有些错误代码很难排除,您将不得不在堆栈溢出网站( [`https://stackoverflow.com/questions`](https://stackoverflow.com/questions) )上搜索答案。 ### 练习 2-12:用三重引号添加多行注释 | ① | `>>> """You're one of a kind. You're my best friend` `.``I've never met someone like you before. It's been wonderful getting to know you. """``"You're one of a kind.\nYou're my best friend.\nI've never met someone like you before.\nIt's been wonderful getting to know you.\n"` | | ② | `>>> cook_noodles = '''How to cook a bowl of instant noodles? Pour 2.5 cups of water in a saucepan,``boil it first, and then``put dried vegetables, powder soup, and noodles. Then boil it for another 5 minutes` `.``Now you can eat delicious instant noodles.'''``>>> print(cook_noodles)``How to cook a bowl of instant noodles? Pour 2.5 cups of water in a saucepan, boil it first and then``put dried vegetables, powder soup, and noodles. Then boil it for another 5 minutes.``Now you can eat delicious instant noodles.` | | ③ | `# Use PEP-8 to keep per line of code less than 80 characters``>>> cook_noodles = '''How to cook a bowl of instant noodles? \``Pour 2.5 cups of water in a saucepan, boil it first, and then put dried \``vegetables, powder soup, and noodles. Then boil it for another 5 minutes.\``Now you can eat delicious instant noodles.'''``>>> print(cook_noodles)``'How to cook a bowl of instant noodles? Pour 2.5 cups of water in a saucepan, boil it first and then put dried vegetables, powder soup, and noodles. Then boil it for another 5 minutes. Now you can eat delicious instant noodles.'` | #### 说明 | ① | 如果用三个引号(三个单引号或双引号)将一个长字符串括起来,则可以输入多行注释。当输入长描述时,这是一种有效的注释方法。 | | ② | 给变量赋值长字符串时,也可以使用三重引号。 | | ③ | 如果代码行太长,请在行尾使用反斜杠(\)继续编写注释或字符串,如下例所示。PEP-8 建议一行代码的长度应该少于 80 个字符。转到下面的 [Python。org](http://python.org) 网站,了解更多关于 PEP-8 写作风格的信息和更多内容:[`https://www.python.org/dev/peps/pep-0008/`](https://www.python.org/dev/peps/pep-0008/) 。 | ### 练习 2-13:使用\作为转义字符来删除特殊字符的含义 | ① | `>>> single_quote_string = 'He said, "arn\'t, can\'t shouldn\'t woundn\'t."'``>>> print(single_quote_string)``He said, "arn't, can't shouldn't wouldn't."` | | ② | `>>> double_quote_string = "He said, \"arn't can't shouldn't wouldn't.\""``>>> print(double_quote_string)``He said, "arn't can't shouldn't wouldn't."` | #### 说明 | ① | 在单引号中使用反斜杠符号作为转义符可以删除特殊字符的含义,并使下一个字符成为普通文本。所以,Python 将`\'`识别为明文`'`。转义字符(或反斜杠)在 Python 编码中起着重要的作用。 | | ② | 您可以在用双引号标记的字符串中使用转义符。由于`'`(单引号)不同于外部的`"`(双引号),所以不需要像示例①中那样多次使用反斜杠转义字符。 | ### 练习 2-14:使用%s 在字符串中输入(注入)值/字符串 | ① | `>>> exam_result = 95``>>> text_message = 'Congratulations! You have scored %s in your exam!'``>>> print(text_message% exam_result)``Congratulations! You have scored 95 in your exam!` | | ② | `>>> wish = 'You need %s to make your wish come true.'``>>> genie = 'a Genie in the bottle'``>>> print(wish% genie)``You need a Genie in the bottle to make your wish come true.` | | ③ | `>>> fast_car = 'Fast cars have %s & %s to make the car go faster.'``>>> part1 = 'a supercharger'``>>> part2 = 'a turbocharger'` | |   | `>>> print(fast_car% (part1, part2))``Fast cars have a supercharger & a turbocharger to make the car go faster.` | | ④ | `>>> ccnp_score = 'My exam scores are %s forENCOR, %s for ENARSI and %s forENAUTO.'``>>> ccnp_score% (95, 92, 90)``'My exam scores are 95 for ENCOR, 92 for ENARSI and 90 for ENAUTO.'``>>> print(ccnp_score% (95, 92, 90))``My exam scores are 95 for ENCOR, 92 for ENARSI and 90 for ENAUTO.` | #### 说明 | ① | 您可以使用`%s`将数字变量输入到字符串信息中。 | | ② | 您可以使用`%s`将字符串变量输入到字符串消息中。 | | ③ | 您可以使用多个`%s`字符将两个或多个变量输入到一个字符串中,并将它们包装成一个元组序列。 | | ④ | 您不必将值定义为变量;首先,您可以直接添加预期的增量,如下例所示。`ccnp_score`变量需要三个字符串(参数),所以我们提供了三个参数(95,92,90)。 | ## 打印、连接和转换字符串 ### 练习 2-15:使用 print()和 len()函数创建一个简单的函数 | ① | `>>> print('Paris baguette')``Paris baguette``>>> bread = 'NY bagel'``>>> print(bread)``NY bagel` | | ② | `>>> aussie = 'meat pie'``>>> print(len(aussie))``8``>>> print(type(aussie[4]))``` | | ③ | `>>> bread = 'naan'``>>> def bread_len():`□□□□ `length = len(bread)`□□□□ `print(length)``>>> bread_len()``4` | #### 说明 | ① | 使用`print()`功能将输出打印到显示器上。 | | ② | 使用`len()`功能检查字符串的长度。`len()`函数甚至将空格算作一个字符串。 | | ③ | 定义一个变量,然后创建一个简单的函数来读取您的面包选择长度并打印字符数。要创建一个函数,总是以`def`开始,以分号(:)结束标题行。然后,下一行放置四个空格,如图所示。(你已经学过缩进了。)要调用函数,只需输入函数名和`()`,在本例中是`bread_len()`。您刚刚创建了您的第一个函数。 | ### 练习 2-16:使用 lower()和 upper()字符串方法 | ① | `>>> "Bagel Is My Favorite Bread!".lower()``'bagel is my favorite bread!'``>>> bread = 'BAGEL'``>>> print(bread.lower())``bagel` | | ② | `>>> "baguette is also my favorite bread.".upper()``'BAGUETTE IS ALSO MY FAVORITE BREAD.'``>>> bread = 'baguette'``>>> print(bread.upper())``BAGUETTE` | #### 说明 | ① | `lower()`和`upper()`都是用于字符串处理的内置方法。字符串方法`lower()`将所有大写字符转换成小写。如果存在小写字符,则返回原始字符串。 | | ② | 字符串方法与字符串方法完全相反。 | ### 练习 2-17:执行字符串连接并使用 str()方法 | ① | `>>> print('Best' + 'friends' + 'last' + 'forever.')``Bestfriendslastforever.``>>> print('Best ' + 'friends ' + 'last ' + 'forever.')``Best friends last forever` `.``>>> print('Best' + ' ' + 'friends' + ' ' + 'last' + ' ' + 'forever.')``Best friends last forever.``>>> print('Best', 'friends', 'last', 'forever.')``Best friends last forever.``>>> print('~'*50)``~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~``>>> love = ('like' * 10)``>>> print(love)``likelikelikelikelikelikelikelikelikelike` | | ② | `>>> time = 30``>>> print('You have' + time + 'minutes left.')``Traceback (most recent call last):``File "", line 1, in ``TypeError: can only concatenate str (not "int") to str`> > >`print('You have ' + str (time) + ' minutes left.')``You have 30 minutes left.` | #### 说明 | ① | 您刚刚练习了使用+号、空格、逗号和*号来操作输出的字符串连接。字符串连接是编码中的一项基本技能。 | | ② | 该变量被赋予一个整数 30。您不能将整数与字符串连接起来,首先,使用`str()`方法将整数转换为字符串,该方法将整数数据类型转换为字符串数据类型。这种转换也称为转换。 | ### 练习 2-18:学习使用花括号和。格式() | ① | `>>> 'She is {} years old.'.format(25)``'She is 25 years old.'``>>> 'She is {{}} years old.'.format()``'She is {} years old.'``>>> 'She is {{}} years old.'.format(25)``'She is {} years old.'``>>> 'She is {{{}}} years old.'.format(25)``'She is {25} years old.'` | | ② | `>>> 'Learning Python 101 is {}.'.format('important')``'Learning Python 101 is important.'``>>> '{} {} {} {} {}'.format ('Learning', 'Python', 101, 'is', 'important.')``'Learning Python 101 is important.'` | | ③ | `>>> '{} | {} | {}'.format ('bread', 'quantity', 'date')``bread | quantity | date``>>> '{} | {} | {}'.format ('bagel', '100', '01/12/2020')``'bagel | 100 | 01/12/2020'` | #### 说明 | ① | 使用`{}`(花括号)和`.format()`方法改变字符串。要在字符串中显示`{}`,必须使用双花括号。要在字符串中显示`{value}`,必须使用三重花括号。 | | ② | 在`.format()`方法中使用多个花括号组成一个字符串。 | | ③ | 您还可以使用花括号和管道符号(|)创建类似表格的格式。 | ### 练习 2-19:用花括号和调整文本位置。格式() | ① | `>>> '{}|{}'.format ('bagel', '10')``'bagel|10'``>>> '{} | {}'.format ('bagel', '10')``'bagel | 10'``>>> '{0:1} | {1:1}'.format ('bagel', '10')``'bagel | 10'` | | ② | `>>> '{0:1} | {0:1}'.format ('bagel', '10')``bagel | bagel``>>> ('{0:1} | {0:1} | {1:1} | {1:1}'.format ('bagel', '10'))``'bagel | bagel | 10 | 10'` | | ③ | `>>> '{0:>1} | {1:1}'.format ('bagel', '10')``'bagel | 10'``>>> '{0:>10} | {1:1}'.format ('bagel', '10')``bagel | 10'``>>> '{0:>20} | {1:1}'.format ('bagel', '10')``bagel | 10'` | | ④ | `>>> '{0:¹⁰} | {1:¹⁰}'.format ('bagel', '10') ' bagel | 10 '``>>> '{0:²⁰} | {1:²⁰}'.format ('bagel', '10')``Bagel   |   10   '``>>> '{0:³⁰} | {1:³⁰}'.format ('bagel', '10')``Bagel  |   10   '` | #### 说明 | ① | 您可以使用花括号内的选项和`format()`方法来调整文本位置。字符串定位特殊字符包括<(向左调整)、^(居中调整)或>(向右调整)。此处的示例显示了如果您将花括号留空或填充,项目的默认索引和定位。 | | ② | 您可以在字符串中的任何位置随意分页。 | | ③ | 大于号(>)用于将字符串移动 x 个空格。`{0:>10}`表示第一个索引项将在位置 10 处结束,依此类推。`{0:>1}`中的第一个整数 0 指的是元组中的第一项`('bagel', '10')`,所以指的是`bagel`。同样的,`{1:1}`中的第一个整数 1 指的是第二项,10。 | | ④ | 这些示例展示了当您使用不同的定位值调整两个字符串并将它们居中时会发生什么。 | ### 练习 2-20:调整小数位数 | ① | `>>> '{0:¹⁰} | {1:10}'.format('pizza', 27.333333)``' pizza | 27.333333'``>>> '{0:¹⁰} | {1:10.2f}'.format('pizza', 27.333333)``'  pizza   |   27.33'` | | ② | `>>> '{0:¹⁰} | {1:10}'.format('Cisco IOS XE 4351', 16.1203)``'Cisco IOS XE 4351 | 16.1203'``>>> '{0:1} | {1:10.2f}'.format('Cisco IOS XE 4351', 16.1203)``'Cisco IOS XE 4351 |   16.12'` | | ③ | `>>> '{0:1} | {1:1.2f}'.format('Cisco IOS XE 4351', 16.1203)``'Cisco IOS XE 4351 | 16.12'``>>> router = ('Cisco IOS XE 4351', 16.1203)``>>> router[0] + ' | ' + str( round(router[1], 2))``'Cisco IOS XE 4351 | 16.12'` | #### 说明 | ① | 使用`nf`,可以格式化小数位数。这里,`n`是小数位数,`f`代表格式说明符。因此,`2f`表示将浮点数减少到两位小数。为了更好地理解格式说明符或本书中的任何其他内容,您必须输入行并从您的键盘和屏幕上学习。 | | ② | 将格式应用于网络示例。 | | ③ | 获得相同结果的另一种方法是在代码中使用串联和舍入方法。先用索引法用`'router[0]`调出路由器名称。二、索引第二项,`float`(思科 IOS XE 版本号)。第三,使用十进制引用为 2 的舍入方法。第四,将缩短的浮点数转换为字符串。最后,使用`+`方法将这些值连接成一个字符串。 | ### 练习 2-21:通过 Input()请求并接收用户输入 | ① | `>>> fav_bread = input('Name of your favorite bread: ')``Name of your favorite bread: pita``>>> print(fav_bread)``Pita``>>> num_bread = input("How many " + fav_bread + " would you like? ")``How many pita would you like? 5``>>> print(num_bread)``5``>>> 'So, you wanted {} {} bread.'.format(num_bread, fav_bread)``'So, you wanted 5 pita bread.'` | #### 说明 | ① | 你可以使用`input()`通过键盘接收用户的输入。前面的例子显示了`input()`函数接受用户输入,并可以将返回的信息存储为变量。你可以从计算机的随机存储器中回忆起存储的信息。 | ### 练习 2-22:改变字符串中的单词或字符 | ① | `>>> your_phone = 'iPhone 12 Pro'``>>> your_phone.split()``['iPhone', '12', 'Pro']``>>> your_phone = your_phone.split()``>>> your_phone``['iPhone', '12', 'Pro']``>>> your_phone[2] = 'ProMax'``>>> your_phone``['iPhone', '12', 'ProMax']``>>> " ".join(your_phone) #` `" " has a whitespace``'iPhone 12 ProMax'` | | ② | `>>> my_phone = 'Galaxy S10 +'``>>> len(my_phone)``12``>>> my_phone = list(my_phone)``>>> my_phone``['G', 'a', 'l', 'a', 'x', 'y', ' ', 'S', '1', '0', ' ', '+']``>>> my_phone[8], my_phone[11] = '2', 'Ultra'``>>> my_phone``['G', 'a', 'l', 'a', 'x', 'y', ' ', 'S', '2', '0', ' ', 'Ultra']``>>> "".join(my_phone) #` `"" has no whitespace``'Galaxy S20 Ultra'` | #### 说明 | ① | 第一个例子教你如何拆分一个字符串和替换一个单词。你的目标是用`ProMax`代替`Pro`这个词。因此,您使用了`split()`方法,然后索引第三个项目`pro`,并将其替换为`ProMax`;然后我们用`" ".join`的方法加上空格把字符串放回一起。你的手机现在是`iPhone 12 ProMax`。 | | ② | 在本练习中,您的目标是将字符串`Galaxy S10 +`转换成`Galaxy S20 Ultra`。由于 Python 字符串的不可变性质,要替换字符串中的字符,必须使用一种变通方法。因此,第 9 个字符1 必须替换为 2,第 12 个字符必须替换为单词`Ultra`。记住,在 Python 中,索引是从 0 开始的,所以索引 8 是第 9 个字符,索引 11 是第 12 个字符。首先,使用`list ()`方法将字符串分割成列表中的单个字符。然后使用索引将 1 和+替换为 2 和 Ultra。最后,使用没有空格分隔符""的`.join`方法连接所有元素。你可能会觉得之前的练习比较邪门。如果你喜欢前面的练习,你可能会喜欢写代码,因为你非常注意细节。 | ### 重述:变量和字符串 以下是变量和字符串的概述: * 字符串是不可变的序列数据类型。 * 可以用单引号或双引号来表示字符串。 * 变量是内存中某个位置的标签。 * 变量可以保存一个值。 * Python 使用基本的变量命名约定来提高代码的可读性和可用性。 * Python 变量必须以字母、单词或带有下划线分隔符的单词开头。它不能以整数、特殊字符或空格开头,如果使用了两个以上的单词,则不能在两个单词之间放置空格。 * 作为最佳实践,避免使用 Python 保留字或函数名作为变量名。 * 函数是为完成特定任务而编写的可重用代码。 * 您已经学习了如何使用内置函数`print()`、`len()`、`str()`和`list()`来处理字符串数据类型。 表 2-3 包含 Python 3.8 中的保留关键字;避免使用这些单词作为变量名。 表 2-3。 Python 3 保留关键字 | 和 | 是吗 | 从 | 不 | 正在… | | --- | --- | --- | --- | --- | | 如同 | 艾列弗 | 全球的 | 或者 | 随着 | | 维护 | 其他 | 如果 | 及格 | 产量 | | 破裂 | 除...之外 | 进口 | 打印 | 错误的 | | 班级 | 高级管理人员 | 在 | 上升 | 真实的 | | 继续 | 最后 | 存在 | 返回 | 没有人 | | 极好的 | 为 | 希腊字母的第 11 个 | 尝试 | 非局部的 | * print 在 Python 2 中是一个关键字,但在 Python 3 中是一个函数。 ![img/492721_1_En_2_Figh_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Figh_HTML.gif)与 Python 2.7 不同,在 Python 3.x 中,`print`语句是一个内置函数,而不是一个保留关键字。 要查看保留的关键字,请在 Python 解释器中键入`help("keywords")`。 要查看完整的内置列表,请在 Python 解释器中键入 **dir(__builtins__)** 。 ## 数字和算术运算符 接下来,您将通过简单的例子继续学习数字、运算符和函数。算术全是数字,数学全是理论;在这里,我们只处理数字。 ### 练习 2-23:使用算术运算符 | ① | `>>> 1 + 2``3``>>> 1 - 2``-1``>>> 1 * 2``2``>>> 1 / 2``0.5` | | ② | `>>> 1 + 2.0``3.0` | | ③ | `>>> 2 ** 3``8` | | ④ | `>>> 7 // 3``2` | | ⑤号 | `>>> 7 % 3``1``>>> 4 % 2``0` | #### 说明 | ① | 加、减、乘、除运算符可以执行数字的计算。 | | ② | 当 Python 计算一个整数和一个浮点数时,结果采用浮点数的形式。 | | ③ | Python 把 2 的 3 次方表示为 2**3。 | | ④ | `//`执行楼层划分。7 包含两批 3,所以正确答案是 2,剩下的 1 丢弃。 | | ⑤号 | 要找到余数,可以使用`%`符号。 | ### 练习 2-24:理解整数和字符串 | ① | `>>> int_3 = 3``>>> str_3 = '3'``>>> total = int_3 + str_3``raceback (most recent call last):``File "", line 1, in total = int_3 + str_3``TypeError: unsupported operand type(s) for +: 'int' and 'str'` | | ② | `>>> total = int_3 + int(str_3)``>>> print(total)``6` | #### 说明 | ① | 将整数赋给一个变量,将字符串赋给另一个变量。使用运算符将两者相加。Python 会返回一个`TypeError`。您只能添加相同类型的对象。 | | ② | 现在使用`int()`方法将字符串转换为整数,它将执行标准计算并返回 6。 | ### 重述:算术运算符 以下是算术运算符的概述: * Python 使用算术运算符来执行整数计算。当与字符串一起使用时,加号(+)可以连接一系列字符串,而乘数(*)可以将一个字符串相乘 n 次。表 2-4 显示了 Python 算术运算符。 表 2-4。 Python 算术运算符 | 操作员 | 意义 | | --- | --- | | + | 添加 | | - | 减法 | | * | 增加 | | / | 分开 | | ** | 关机 | | // | 楼层划分 | | % | 剩余物 | * 输入数字时,不要使用引号。 * 如果数字在引号中,Python 会将对象识别为字符串。 * 要将字符串转换成整数,请使用`int()`函数。 * 要将字符串转换成十进制,使用`float()`功能。 ## 布尔和关系运算符 我们现在来看看布尔和关系运算符。 ### 练习 2-25:使用布尔值 | ① | `>>> a = True``>>> b = False` | |   | `>>> print(a)``True``>>> print(b)``False``>>> type (a)````>>> type (b)``` | | ② | `>>> type (True)````>>> print((1).__bool__())``True``>>> type (False)````>>> print((0).__bool__())``False` | #### 说明 | ① | 布尔值可以有值`True`或`False`。布尔测试某些条件的`True`或`False`值。 | | ② | 在布尔运算中,常数 1 代表`True`,0 代表`False`。您可以使用`bool`方法来测试布尔运算中的值 1 或 0。被定义为假的常数包括`None`和`False`。任何数值类型的零—0,0.0,0j,decimal(0),integer(0.1)—都是`False`。同样,空序列和集合—“”、()、[]、{}、set()、range(0)—也是`False`。 | ### 练习 2-26:使用关系运算符 下面是一个布尔表,作为练习。不要浪费时间盯着布尔表去理解布尔运算符。相反,做下面的练习来理解布尔运算符。 | ① | `>>> 1 == 2``False` | `# Equal to` | |   | `>>> 1 > 2``False` | `# Greater than` | |   | `>>> 1 >= 2``False` | `# Greater or equal to` | |   | `>>> 1 < 2``True` | `# Less than` | |   | `>>> 1 <= 2``True` | `# Less or equal to` | |   | `>>> 1 != 2``True` | `# Not equal to` | #### 说明 | ① | 关系运算符是不言自明的,所以你练习得越多,对它们的使用就会越好。 | ### 练习 2-27:使用布尔表达式来测试真或假 | ① | `>>> True and True is True``True``>>> True and False is False``True``>>> False and True is False``False``>>> False and False is False``False``>>> not True is False``True``>>> not False is True``True` | #### 说明 | ① | 在前面的例子中,Python 测试了每个语句的`True`和`False`,以便更好地理解布尔值。盯着书看可能没有任何意义;在 Python 解释器窗口中输入布尔语句。 | ### 练习 2-28:使用逻辑(成员)运算符 | ① | `>>> True and False or not False``True``>>> True and False or True``True``>>> False or True``True``>>> True or False``True` | #### 说明 | ① | 前面的条件总是返回结果作为`True`。逻辑运算符`and`、`or`和`not`在 Python 中也被称为*成员运算符*。 | ### 练习 2-29:用()改变运算顺序 | ① | `>>> True and False or not False``True` | | ② | `>>> (True and False) or (not False)``True` | | ③ | `>>> ((True and False) or (not False))``True` | #### 说明 | ① | 这个例子是从左到右的简单条件测试。 | | ② | 使用括号,您可以改变操作的顺序,尽管在本例中,预期的输出是相同的,`True`。 | | ③ | 使用括号,你可以改变求值的流程。 | ## 控制语句:if、elif 和 else 我们来谈谈控制语句。 ### 练习 2-30:使用 if 和 else | ① | `>>> if 1 < 2:``...     print('One is less than two.')``...``One is less than two.` | | ② | `>>> if 1 > 2:``...     print('One is bigger than two.')``...``>>>` | | ③ | `>>> if 1 > 2:``...     print('One is bigger than two.')``... else:``...     print('One is NOT bigger than two.')``...``One is NOT bigger than two.` | #### 说明 | ① | `if`语句可以是单个语句。如果条件是`True`,它将运行下一条语句,也就是`print`语句。 | | ② | 如果`if`语句是`False`,它将从`if`循环中退出。如您所见,条件语句以分号(:)结尾。 | | ③ | 这是一个 if 和 else 的例子;不满足第一个条件,因此 else 语句将捕获所有其他条件。 | ### 练习 2-31:使用 if、elif 和 else | ① | `>>> age = 21``>>> if age >= 18:``...     print('You are old enough to get your driver\'s license.')``...``You are old enough to get your driver's license.` | | ② | `>>> age = 17``>>> if age >= 18:``...     print('You are old enough to drive a car.')``else:``...     print('You are too young to drive a car.')``...``You are too young to drive a car.` | | ③ | `>>> age = 100``>>> if age <18:``...     print('You are too young to drive a car.') elif age> 99:``...     print('You are too old to drive a car.')``else:``...     print('You are in an eligible age group, so you can drive a car.')``...``You are too old to drive a car.` | #### 说明 | ① | 在本练习中,您仅使用了一条`if`语句。 | | ② | 在本练习中,您使用了`if`和`else`语句。 | | ③ | 在最后一个练习中,您使用了`if`、`elif`和`else`语句。如果有更多的条件,可以根据需要使用任意多的`elif`语句。 | ### 练习 2-32:用 if、elif 和 else 编写代码 第一步。对于本练习,在您的 Python shell 中,转到文件➤新文件。在 Python 的内置文本编辑器中输入源代码。 | ① | `# Enter the following code and save it as a .py file format. # ex2_32.py``q1 = input ('What is your legal age?') age = int(q1)``if age < 16:``...     print('You are too young to take a driving test.')``elif age > 99:``...     print('You are too old to take a driving test.')``else:``...     print('You\'re in the right age group to take a driving test.')``...` | 第二步。从文本编辑器的菜单中,转到文件➤保存,并将文件另存为`ex2_32.py`。之后,将你的代码保存为 Python 根文件夹`C:\Python39`下的`driverage.py`。在 Python 文本编辑器中,转到运行➤运行模块或按 F5 键运行脚本。然后,Python shell 将提示您输入年龄,如下所示: | ② | `What is your legal age? 15``You are too young to take a driving test.` | | ③ | `What is your legal age? 18``You're in the right age group to take a driving test.` | | ④ | `What is your legal age? 100``You are too old to take a driving test.` | #### 说明 | ① | 这段源代码使用了我们到目前为止学到的 Python 基础知识,并使用了`if`、`elif`和`else`语句进行流控制。此外,需要用户输入来测试条件并相应地运行下一行代码。 | | ② | ex2_32.py 脚本的示例 1 运行。 | | ③ | ex2_32.py 脚本的示例 2 运行。 | | ④ | ex2_32.py 脚本的示例 3 运行。如果您在 Linux/macOS 上运行这个脚本,您可以在保存 Python 源文件的地方键入`python3 ex2_32.py`来运行代码。 | ### 概述:布尔和条件 这里是一个回顾: * 布尔数据类型处理条件的真值,即某事物是否为`True`或`False`。 * 关系运算符将一个数字与另一个数字进行比较,得出布尔值。 * 在布尔运算中,可以使用圆括号( )来改变运算的顺序。 * 使用布尔逻辑运算符表达更复杂的条件,如`and`和`not`。 * 布尔运算中的逻辑运算符`and`、`or`、`not`也称为*隶属运算符*。 * `and`如果两个或多个条件都为真,则为真。 * 即使几个条件中只有一个为真,也为真。 * `not`计算所用运算符的逆条件。 * 在布尔运算中,运算的执行顺序是`not`、`and`,然后是`or`。 * 在 Python 代码中,`if`、`elif`和`else`等控制语句与布尔运算符一起使用。 ## 功能 先说函数。 ### 练习 2-33:定义函数 | ① | `>>> def say_hello():``...      print('Hello')``...``>>> say_hello()``Hello` | | ② | `>>> say_goodbye()``Traceback (most recent call last): File "", line 1, in ``NameError: name 'say_goodbye' is not defined` | #### 说明 | ① | 在 Python 中,使用单词`def`定义函数,并且总是以冒号结尾。您可以按以下格式创建新函数:`def function_name():` `# Code block` | | ② | 如果你使用一个没有定义的函数,将会遇到一个`NameError`。 | ### 练习 2-34:为函数分配默认值 | ① | `>>> def say_hello(name):``...     print('Hello {}.'.format (name))``...``>>> say_hello('Hugh')``Hello Hugh.` | | ② | `>>> say_hello()``Traceback (most recent call last):``File "", line 1, in ``TypeError: say_hello() missing 1 required positional argument: 'name'` | | ③ | `>>> def say_hello(name = 'son'):``...     print('Hi {}.'. format (name))``...``>>> say_hello()``Hi son.``>>> say_hello('Hugh')``Hi Hugh.` | #### 说明 | ① | 这是一个当你输入一个名字时返回一个`Hello`的函数。 | | ② | 如果您忘记输入姓名,将返回`TypeError`。 | | ③ | 在本例中,您已经指定了一个默认名称,使代码在没有`TypeError`的情况下运行。您可以将其视为一种错误处理机制。我们将在后面的练习中学习错误处理。 | ### 练习 2-35:定义问候和再见功能 | ① | `>>> def say_hello(f_name, s_name):``...     print('Hello {} {}!'. format (f_name, s_name))``...``>>> say_hello ('Michael', 'Shoesmith')``Hello Michael Shoesmith!``>>> say_hello ('Michael')``Traceback (most recent call last):``File "", line 1, in ``TypeError: say_hello() missing 1 required positional argument: 's_name'` | | ② | `>>> >>> say_hello(f_name='Leah', s_name="Taylor")``Hello Leah Taylor!``>>> say_hello(s_name = 'Johnson', f_name = 'Caitlin')``Hello Caitlin Johnson!` | | ③ | `>>> def say_goodbye(f_name, s_name = 'Doe'):``...    print('Goodbye {} {}!'. format (f_name, s_name))``...``>>> say_goodbye('John')``Goodbye John Doe!``>>> say_goodbye('John', 'Citizen')``Goodbye John Citizen!` | #### 说明 | ① | 在本例中,您定义了带有两个变量的`say_hello`函数。这意味着当函数运行时,它会有两个位置参数。如果你只给出一个参数,你会遇到`TypeError`缺少一个参数。 | | ② | 如果使用变量名来调用函数,变量的顺序并不重要,因为函数使用命名的变量。 | | ③ | 您还可以为函数分配一个默认变量值,以接受甚至是单个位置参数的响应。在这个例子中,一个或两个参数在没有`TypeError`的情况下被接受和处理。这可能是一种可接受的避免错误的策略,可以让您的代码继续运行。 | ### 练习 2-36:使用奇数或偶数函数 | ① | `>>> def odd_or_even(number):``...     if number%2 == 0:``...         return 'even'``...     else:``...         return 'odd'``...``>>> odd_or_even(3)``'odd'``>>> odd_or_even(4)``'even'` | | ② | `>>> def even_num(number):``...     if number%2 == 0:``...         return True``...     else:``...         return False``...``>>> even_num(1)``False``>>> even_num(2)``True` | #### 说明 | ① | 您刚刚创建了一个简单的应用,返回一个数字是偶数还是奇数。不要看函数的简单性;想想如何在工作中应用。 | | ② | 您可以调整示例①,使其成为一个`True`或`False`函数。每天努力学习 Python 的基础知识,思考使用这些函数的场景或应用。 | ### 练习 2-37:在函数中嵌套一个函数 | ① | `>>> def name():``...     n = input('Enter your name: ')``...         return n``...``>>> def say_name(n):``...     print('Your name is {}.'.format(n))``...``>>> def say_the_name():``...     n = name()``...         say_name(n) #` `The first function is nested here.``...``>>> say_the_name()``Enter your name: Michael Shoesmith Your name is Michael Shoesmith.` | #### 说明 | ① | 您已经创建了两个函数,然后在第三个函数上,您嵌套(重用)了它们。这是一个简单的练习,但是如果一个函数冗长而复杂,您可以将代码行保存为一个单独的文件,并将一个特定的函数作为一个定制的模块导入。您将在后面的章节中学习模块,因此在这一节中,尝试将重点放在 Python 语法上。 | ### 概述:功能 这里是一个回顾: * 使用前定义一个函数。基本函数语法以`def function_name(parameter_name):`开头。 * 一个函数可以用可重用的主代码中的小代码执行任何动作,并返回数据。 * 函数可以带参数,或者您可以设置默认参数值来使用任意参数。 * 函数可以用来控制脚本的流程。 * 使用内置函数`help()`然后输入`def`可以获得更多帮助。 ## 列表 我们来谈谈列表。 ### 练习 2-38:创建列表和索引项 | ① | `>>> vehicles = ['car', 'bus', 'truck']``>>> print(vehicles[0])``car``>>> print(vehicles[1])``bus``>>> print(vehicles[2])``Truck` | | ② | `>>> vehicles = ['car', 'bus', 'truck']``>>> vehicles[0] = 'motorbike'``>>> vehicles``['motorbike', 'bus', 'truck']` | | ③ | `>>> print(vehicles[-1])``truck``>>> print(vehicles[-2])``bus``>>> print(vehicles[-3])``Motorbike` | | ④ | `>>> vehicles``['motorbike', 'bus', 'truck']``>>> vehicles[0] = ['sedan', 'wagon', 'convertible', 'SUV']``>>> vehicles``[['sedan', 'wagon', 'convertible', 'SUV'], 'bus', 'truck']` | | ⑤号 | `>>> cars = ['sedan', 'wagon', 'SUV', 'hatchback']``>>> for car in range(len(cars)):``...     print('{} is at position {}. '.format(cars[car], car))``...``sedan is at position 0\. wagon is at position 1\. SUV is at position 2\. hatchback is at position 3.` | #### 说明 | ① | 在 Python 中,列表是用方括号写的。在此示例中,仅使用了字符串项,但列表支持所有其他数据类型。索引从 0 开始并增加 1,因此到第`truck`页,这是列表中的第三项,索引[2]。 | | ② | 列表是有序且可变的集合。因此您可以使用索引方法替换项目。在本练习中,`car`已被替换为`motorbike`。 | | ③ | 您可以使用减号向后索引项目。更多切片示例将很快出现。 | | ④ | 该列表还可以包含大多数其他数据类型。本例显示了作为父列表`vehicle`一部分的汽车列表。 | | ⑤号 | 在示例①中,您已经看到索引从 0 开始。快速编写一个函数来检查项目位置,并查看它是否从 0 开始。 | ### 练习 2-39:在列表中使用追加、扩展和插入 | ① | `>>> cars = ['sedan', 'SUV', 'hatchback']``>>> cars.append('convertible')``>>> cars``['sedan', 'SUV', 'hatchback', 'convertible']` | | ② | `>>> cars.extend(['crossover', '4WD'])``>>> cars``['sedan', 'SUV', 'hatchback', 'convertible', 'crossover', '4WD']` | | ③ | `>>> cars.insert(1, 'wagon')``>>> cars``['sedan', 'wagon', 'SUV', 'hatchback', 'convertible', 'crossover', '4WD']` | #### 说明 | ① | 在列表末尾添加一个项目。 | | ② | 要在列表末尾添加多个项目,使用`extend()`功能。 | | ③ | 若要在特定位置插入项目,请使用索引号来插入新项目。在本例中,`wagon`被插入到索引 1 中,并将其他项目推到右边的索引。 | ## 限幅 先说切片。 ### 练习 2-40:分割列表 | ① | `>>> bread = ['bagels', 'baguette', 'ciabatta', 'crumpet', 'naan', 'pita', 'tortilla']``>>> some_bread = bread[1:3]``>>> some_bread``['baguette', 'ciabatta']``>>> print('Some Bread: {}'.format (some_bread))``Some Bread: ['baguette', 'ciabatta']` | | ② | `>>> first_two = bread[0:2]``>>> first_two``['bagels', 'baguette']` | | ③ | `>>> first_three_bread = bread[:3]``>>> print(first_three_bread)``['bagels', 'baguette', 'ciabatta']` | | ④ | `>>> last_two_bread = bread[-2:]``>>> print('Last two bread: {}'.format (last_two_bread))``Last two bread: ['pita', 'tortilla']` | | ⑤号 | `>>> bread = ['bagels', 'baguette', 'ciabatta']``>>> ciabatta_index = bread.index('ciabatta')``>>> print(ciabatta_index)``2` | #### 说明 | ① | 您可以使用切片方法来调用列表中的项目。在这个例子中,我们想要索引条目 1 和 2,所以我们必须使用[1:3]。包括第一个索引项,但不包括最后一个索引项。您也可以使用`print()`功能美化您的输出。 | | ② | 如果想要前两项,使用[0:2]作为切片索引。 | | ③ | 如果不指定起始索引号,Python 将从索引 0 开始分页。 | | ④ | 您也可以使用负索引向后翻页。 | | ⑤号 | 您还可以创建一个变量,并检查列表中某个项目的索引号。 | ## 异常和错误处理 我们来谈谈异常和错误处理。 ### 练习 2-41:避免值错误错误 | ① | `>>> bread = ['bagels', 'baguette', 'ciabatta']``>>> crumpet_index = bread.index('crumpet')``Traceback (most recent call last): File "", line 1, in ValueError: 'crumpet' is not in list` | #### 说明 | ① | 如果你在页面上看到一个不在列表中的项目,你会遇到`ValueError`。 | ### 练习 2-42:用列表中的 try 和 except 处理错误 学习错误处理的概念。 Hint 当一个错误发生时,你的程序会突然停止。当 Python 遇到错误时,它会将其检测为停止并退出应用的标志。如果您知道预期会出现什么错误,那么在大多数情况下,您会希望控制如何处理错误。不要让 Python 决定如何处理错误。 Note 你要把下面的代码保存在一个文件里,另存为`ex2_42.py`。 | ① | `>>> bread = ['bagels', 'baguette', 'ciabatta']``>>> try:``...     crumpet_index = bread.index('crumpet')``... except:``...     crumpet_index = 'No crumpet bread found.'``...     print(crumpet_index)``...``No crumpet bread found` `.` | | ② | 打开 Notepad++输入如下几行代码,如图 2-5 所示。然后将文件保存为 C:\Python39\文件夹下的 ex2_42.py。`# ex2_42.py``bread = ['bagels', 'baguette', 'ciabatta']``try:``crumpet_index = bread.index('crumpet')``except:``crumpet_index = 'No crumpet bread found.'``print(crumpet_index)`保存文件后,按 Ctrl+F6 组合键运行脚本。#输出:`C:\Python39\python.exe "C:\Python39\ex2_42.py"``Process started (PID=6916) >>>``No crumpet bread found.``<<< Process finished (PID=6916). (Exit code 0)``================ READY ================` | ![img/492721_1_En_2_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Fig5_HTML.jpg) 图 2-5。 记事本++上的 Python 编码示例 #### 说明 | ① | 当您在应用中遇到错误时,程序必须继续运行应用的其余部分,否则它会停止运行以防止出现更严重的问题。错误是脚本的一个建设性部分,它们帮助您的应用停止,因此不会导致更多的问题。因此,作为一名程序员,您希望知道哪些错误是可以预料的,以及当它们发生时您希望如何处理它们。如果您知道如何处理类似于本练习中前一个示例中的`ValueError`的错误,您将拥有更干净的 Python 代码。您可以自定义并处理您想要表达的错误消息。在前面的示例中,错误消息明确指出分页面包类型 crumpet 不存在。 | | ② | 在这个例子中,使用文本编辑器比直接用 Python IDLE 编写要容易得多。图中显示了 Notepad++中的源代码`ex2_42.py`,并通过按 Ctrl+F6 键运行代码。 | ### 练习 2-43:在带有自定义异常的列表中查找项目的索引 Note 用给定的练习名保存代码,并在 Python 提示符下运行它。所有代码都可以从 GitHub 站点下载。练习 2-43 ①的文件名为`ex2_43_1.py`,练习 2-43 ②的文件名为`ex2_43_2.py`。 | ① | `# ex2_43_1.py``bread = ['bagels', 'baguette', 'ciabatta', 'crumpet']``try:``crumpet_index = bread.index ('crumpet')``except:``crumpet_index = 'No crumpet bread was found.'``print(crumpet_index)``# Output from Notepad++``C:\Python39\python.exe "C:\Python39\ex2_43_1.py"``Process started (PID=5852) >>>``<<< Process finished (PID=5852). (Exit code 0)``================ READY ================` | | ② | `# ex2_43_2.py``bread = ['bagels', 'baguette', 'ciabatta', 'naan']``try:``crumpet_index = bread.index ('crumpet')``except:``crumpet_index = 'No crumpet bread was found.'``print(crumpet_index)``# Output from Notepad++``C:\Python39\python.exe "C:\Python39\ex2_43_2"``Process started (PID=4580) >>>``No crumpet bread was found.``<<< Process finished (PID=4580). (Exit code 0)``================ READY ================` | #### 说明 | ① | 在这个例子中,我们试图找到`crumpet`的索引号。`bread.index('crumpet')`将查找物品的索引号。在输出中,返回索引 3。 | | ② | 如果我们用`naan` bread 替换`crumpet`并运行脚本,那么它将返回一个定制的异常错误,如输出所示。 | ### 练习列表 练习到目前为止你所学的列表概念。我鼓励你输入显示的每个单词和字符。当你打字的时候,试着猜猜这个练习想教会你什么。你可能会累,但没有人会为你练习这些练习;你必须自己做所有的事情。 ### 练习 2-44:练习列表 | ① | `>>> shopping_list1 = 'baseball hat, baseball shoes, sunglasses, baseball bat, sunscreen lotion, baseball meat'``>>> print(shopping_list1)``baseball hat, baseball shoes, sunglasses, baseball bat, sunscreen lotion, baseball meat``>>> type(shopping_list1)``` | | ② | `shopping_list2 = ['baseball hat', 'baseball shoes', 'sunglasses', 'baseball bat', 'sunscreen lotion', 'baseball meat']``>>> print(shopping_list2 [2])``sunglasses``>>> type(shopping_list2)``` | | ③ | `>>> shopping_list2 = ['baseball hat', 'baseball shoes', 'sunglasses', 'baseball bat', 'sunscreen lotion', 'baseball meat']``>>> shopping_list2.pop(5)``'baseball meat'``>>> shopping_list2``['baseball hat', 'baseball shoes', 'sunglasses', 'baseball bat', 'sunscreen lotion']` | | ④ | `>>> print(shopping_list2 [2])``sunglasses``>>> print(shopping_list2 [2: 5])``['sunglasses', 'baseball bat', 'sunscreen lotion']` | | ⑤号 | `>>> shopping_list2 [2] = 'ball'``>>> print(shopping_list2)``['baseball hat', 'baseball shoes', 'ball', 'baseball bat', 'sunscreen lotion']` | | ⑥ | `>>> some_numbers = [1, 3, 5]``>>> some_strings = ['which', 'Olympic', 'sports']``>>> numbers_and_strings = ['which', 1, 'Olympic', 3, 'sports', 5]` | | ① | `>>> numbers = [3, 6, 9, 12]``>>> strings = ['soccer', 'baseball', 'basketball', 'swimming']``>>> new_list = [numbers, strings]``>>> print(new_list)``[[3, 6, 9, 12], ['soccer', 'baseball', 'basketball', 'swimming']]` | | ⑧ | `>>> summer_sports = ['swimming', 'diving', 'baseball', 'basketball', 'cricket']``>>> summer_sports.append ('beach volleyball')``>>> print(summer_sports)``['swimming', 'diving', 'baseball', 'basketball', 'cricket', 'beach volleyball']` | | ⑵ | `>>> print(summer_sports)``['swimming', 'diving', 'baseball', 'basketball', 'cricket', 'beach volleyball']``>>> del summer_sports [1]``>>> print(summer_sports)``['swimming', 'baseball', 'basketball', 'cricket', 'beach volleyball']` | | ⑩ | `>>> summer_sports = ['swimming', 'baseball', 'basketball', 'cricket', 'beach volleyball']``>>> winter_sports = ['skiing', 'ice skating', 'ice hockey', 'snowboarding']``>>> print(summer_sports + winter_sports)``['swimming', 'baseball', 'basketball', 'cricket', 'beach volleyball', 'skiing', 'ice skating', 'ice hockey', 'snowboarding']` | #### 说明 | ① | 您刚刚将`shopping_list1`创建为一个字符串。 | | ② | `Shopping_list2`是一个列表,其中的项被括在 中,这是一个方括号集。使用索引返回太阳镜。 | | ③ | 索引不会改变列表,但是如果你使用`pop()`方法,它将永久地取出被调用的项目。 | | ④ | 使用单个索引或一系列索引,可以从列表中调出项目。 | | ⑤号 | 您可以使用索引作为可变对象向列表中插入一个新项。 | | ⑥ | 该列表可以包含数字、字符串或唯一项目的组合。 | | ① | 一个列表可以存储另一个列表。 | | ⑧ | 你可以使用`append()`方法连接两个列表,使它们成为一个列表。 | | ⑵ | 使用删除和索引来删除您要从列表中删除的项目。 | | ⑩ | 您可以使用+运算符连接列表。 | ## 使用 for 循环和 while 循环 再来说说循环。 ### 练习 2-45:使用 for 循环的 upper()和 capitalize()方法 | ① | `>>> bread_type = ['bagels', 'baguette', 'ciabatta']``>>> for bread in bread_type:``...     print(bread.upper())``...``BAGELS``BAGUETTE``CIABATTA` | | ② | `>>> bread_type = ['bagels', 'baguette', 'ciabatta']``>>> for bread in bread_type:``...     print(bread.capitalize())``...``Bagels``Baguette``Ciabatta` | #### 说明 | ① | 顾名思义,`for`循环遍历列表,直到列表中的最后一项被分页。此示例首先调用索引为 0 的项,然后返回到循环,调用索引为 1 的项,然后调用最后一个项,其索引为 2。另外,追加`upper()`会将返回值转换成大写。 | | ② | 你也可以用 Python 的`capitalize()`方法把每个面包的第一个字母改成大写字母。 | ### 练习 2-46:使用 while 循环和 len()函数 | ① | `>>> basket = ['bagels', 'baguette', 'ciabatta', 'crumpet', 'naan', 'pita', 'tortilla']``>>> bread = 0``>>> while bread < len(basket):``...     print(basket[bread], end=" ") # " " contains a whitespace``...     bread += 1``...``bagels baguette ciabatta crumpet naan pita tortilla >>>` | #### 说明 | ① | while 循环和 len()函数用于调出并打印每块面包。这个`while`循环使用索引并循环,直到列表为空。虽然`len()`函数返回一个对象中的项数;在这个例子中,它与`for`循环一起使用,返回每个项目,直到列表为空。注意`end=" "`用于在单行上打印输出。 | ## 排序和范围 我们来谈谈排序和范围。 ### 练习 2-47:在列表中使用 sort()和 sorted() | ① | `>>> bread = ['naan', 'baguette', 'tortilla', 'ciabatta', 'pita']``>>> bread.sort()``>>> bread``['baguette', 'ciabatta', 'naan', 'pita', 'tortilla']` | | ② | `>>> bread = ['naan', 'baguette', 'tortilla', 'ciabatta', 'pita']``>>> bread_in_order = sorted(bread)``>>> bread_in_order``['baguette', 'ciabatta', 'naan', 'pita', 'tortilla']``>>> bread``['naan', 'baguette', 'tortilla', 'ciabatta', 'pita']` | #### 说明 | ① | 当您在列表上使用`sort()`函数时,它会将项目从 A 到 Z 永久排序。 | | ② | `sorted()`函数只是暂时对 A 到 Z 进行排序。它不会改变原始数据的顺序。对于 Python 程序员新手来说,`sort()`和`sorted()`的不同效果似乎是微不足道的,不会被注意到,但是在编程基础中,`sorted()`将是优于`sort()`方法的推荐方法,因为它不会永久地改变原始项目;您可以将原始项目和新创建的项目作为对象重复使用。 | ### 练习 2-48:链接两个列表 | ① | `>>> bread = ['naan', 'baguette', 'tortilla', 'ciabatta', 'pita']``>>> more_bread = ['bagels', 'crumpet']``>>> all_bread = bread + more_bread``>>> print(all_bread)``['naan', 'baguette', 'tortilla', 'ciabatta', 'pita', 'bagels', 'crumpet']``>>> all_bread.sort()``>>> print(all_bread)``['bagels', 'baguette', 'ciabatta', 'crumpet', 'naan', 'pita', 'tortilla']``>> all_bread.reverse()``>>> all_bread``['tortilla', 'pita', 'naan', 'crumpet', 'ciabatta', 'baguette', 'bagels']` | #### 说明 | ① | 在这个例子中,我们创建了两个列表,然后将它们合并成一个列表。然后我们用`sort()`函数把列表中的条目从 A 到 Z 组织起来,最后我们用`reverse()`方法把列表的顺序从 Z 反转到 A。 | ### 练习 2-49:使用 len()函数计算列表长度 | ① | `>>> bread = ['bagels', 'baguette', 'ciabatta']``>>> print(len(bread))``3``>>> bread.append('naan')``>>> print(len(bread))``4` | #### 说明 | ① | 使用`len()`功能检查列表中有多少种面包类型。接下来,使用`append`方法添加另一种面包类型,然后使用`len()`功能重新检查面包类型的数量。增加了 1。 | ### 练习 2-50:使用 range()和 for 循环 | ① | `>>> for number in range (3):``...   print(number)``...``0``1``2` | | ② | `>>> for number in range (2, 5):``...   print(number, end=" ")``...``2 3 4` | | ③ | `>>> for number in range (1, 8, 2):``...     print(number, end=" ")``...``1 3 5 7` | | ④ | `>>> for bread in ['bagels', 'baguette', 'crumpet', 'naan']:``...     print(bread, end=" ")``...``bagels baguette crumpet naan` | | ⑤号 | `>>> for bread in ('bagels', 'baguette', 'crumpet', 'naan'):``...     print(bread, end=" ")``...``bagels baguette crumpet naan` | #### 说明 | ① | 如果在`range()`函数中使用一个`for`循环,就可以轻松地调出列表项。`range(3)`表示不超过 3,因此本示例在 0、1 和 2 之间循环。 | | ② | 使用逗号,您可以指定从哪里开始和从哪里结束。在这个`for`循环示例中,第一个参数(digit)表示第一个数字,第二个参数(digit)表示结束循环的上限数字,所以最后一个数字总是会是 n-1,在这个示例中是 4。 | | ③ | 在前面的示例中,您使用了三个参数来遍历一系列数字。第一个和最后一个数字的含义与前面例②中解释的含义相同,但最后一个数字 2 代表间隔或频率。从 1 开始,它将只循环到奇数,直到达到最大值 8。因此,不出所料,Python 只打印奇数 1、3、5、7。您必须很好地掌握`for`循环,以使您的 Python 脚本更好地为您工作。 | | ④ | 您正在对一个列表应用`for`循环。您没有使用`range()`函数,而是使用了一个列表,其中包含了一些条目。 | | ⑤号 | 您正在对一个元组应用`for`循环。那么,这个和前面的例子有什么区别呢?在这些例子中你感觉不到,但区别在于速度。因为元组是不可变的,并且索引指针比列表少,所以在遍历元组中的每一项时,它们可以更快地处理数据。如果你需要处理一百万条数据,那么速度就很重要,所以使用元组而不是列表。 | ### 练习 2-51:将字符串列表用于带参数的 loop()和 range() | ① | `>>> bread = ['bagels', 'baguette', 'ciabatta', 'crumpet', 'naan', 'pita', 'tortilla']``>>> for number in range (0, len (bread), 2):``...     print(bread [number] , end=" ")``...``bagels ciabatta naan tortilla` | | ② | `>>> bread = ['bagels', 'baguette', 'ciabatta', 'crumpet', 'naan', 'pita', 'tortilla']``>>> for number in range (0, len (bread), 3):``...     print(bread [number] , end=" ")``...``bagels crumpet tortilla` | | ③ | `>>> bread = ['bagels', 'baguette', 'ciabatta', 'crumpet', 'naan', 'pita', 'tortilla']``>>> for number in range (0, len (bread), 5):``...      print(bread [number] , end=" ")``...``bagels pita` | #### 说明 | ① | 在这个例子中,在一个`range()`函数中有一个嵌套的`len()`函数,可以循环遍历一个有三个参数的面包列表,类似于练习 2-50 的例子③。由于最后一个参数是 2,它将遍历面包列表,索引项 1、3、5 和 7。 | | ② | 这与上一个示例相同,但是步进值为 3,索引项为百吉饼、松饼和玉米粉圆饼。 | | ③ | 这是步进值为 5 的同一个示例,因此该示例打印出第一个值和第六个值,即百吉饼和皮塔饼。 | ### 回顾:列表和循环 这里是一个回顾: * 创建变量并将其分配给列表时,请用方括号[ ]将元素括起来,并用逗号分隔列表中的每一项。典型的列表语法如下所示: ```py List_name = [element_1, element_2, ..., element_n] ``` * 列表项可以按从 0 到 n 的索引号进行索引,若要对列表中的第一项进行索引,请使用索引号 0。若要从最后一项开始对某项进行索引,请使用索引号-1。 * 使用切片来索引列表的一部分。例如,使用 List_Name [3,6]。 * 使用 for `loop()`来索引列表中的数字范围。 * 只要条件为`True`,while 循环就会继续运行,只有当条件变为`False`时才会停止。 * 您可以使用`sort()`和`sorted()`列表方法对列表进行排序。 * 如果使用像`range()`这样的内置函数,就可以索引序列号。 * 使用`try`和`except`代码块处理 Python 异常错误。 ## 元组 先说元组。 ### 练习 2-52:看一些基本的元组例子 | ① | `>>> tuple1 = (0, 1, 3, 6, 9)``>>> tuple2 = ('w', 'x', 'y', 'z')``>>> tuple3 = (0, 1, 'x', 2, 'y', 3, 'z')` | | ② | `>>> tuple1 = (0, 1, 3, 6, 9)``>>> tuple [1] = 12``Traceback (most recent call last):``File "", line 1, in ``TypeError: 'type' object does not support item assignment` | | ③ | `>>> tuple1 = (0, 1, 3, 6, 9)``>>> tuple2 = ('w', 'x', 'y', 'z')``>>> print(tuple1 + tuple2)``(0, 1, 3, 6, 9, 'w', 'x', 'y', 'z')` | #### 说明 | ① | 这些是元组的例子。请注意,元组几乎与列表完全相同,只是条目被一组圆括号( )括起来。请注意,它们在美学上是不同的。 | | ② | 你已经尝试把 1 改成 12,tuple 会用一个`TypeError`把假人吐回给你。因此,元组在 Python 中被称为*不可变顺序对象*。 | | ③ | 但是您仍然可以使用+符号将两个元组连接在一起。 | ### 练习 2-53:将元组转换为列表 | ① | `>>> tuple3 = (0, 1, 'x', 2, 'y', 3, 'z')``>>> list (tuple3)``[0, 1, 'x', 2, 'y', 3, 'z']` | | ② | `>>> tuple1 = (0, 1, 3, 6, 9)``>>> tuple2 = ('w', 'x', 'y', 'z')``>>> list (tuple1 + tuple2)``[0, 1, 3, 6, 9, 'w', 'x', 'y', 'z']` | #### 说明 | ① | 要将一个元组变成一个列表,使用`list(tuple_name)`。 | | ② | 您可以合并两个或更多元组,并且仍然使用`list()`转换技术来转换它们。 | ### 练习 2-54:确定一个元组是否不可变 | ① | `>>> days_of_the_week = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday')>>> for day in days_of_the_week:``...     print(day, end=" ")``...``Monday Tuesday Wednesday Thursday Friday Saturday Sunday` | | ② | `>>> days_of_the_week = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday')``>>> days_of_the_week [0] = 'Funday'``Traceback (most recent call last):``File "", line 1, in ``TypeError: 'tuple' object does not support item assignment` | | ③ | `>>> days_of_the_week = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday')``>>> print(days_of_the_week)``('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday')``>>> del days_of_the_week``>>> print(days_of_the_week)``Traceback (most recent call last):``File "", line 1, in ``NameError: name 'days_of_the_week' is not defined` | #### 说明 | ① | 这是一个简单的元组示例,包含一周中的几天。 | | ② | 当你试图用一个条目更新另一个条目时,Python 会返回一个`TypeError`来提醒你一个元组在 Python 中是一个不可变的对象。 | | ③ | 尽管元组是不可变的,但这并不意味着您不能删除整个元组。使用`del tuple_name`删除一个元组。 | ### 练习 2-55:将元组转换为列表,将列表转换为元组 | ① | `>>> weekend_tuple = ('Saturday', 'Sunday')``>>> weekend_list = list (weekend_tuple)``>>> print('weekend_tuple is {}.'. format (type (weekend_tuple)))``weekend_tuple is .``>>> print('weekend_list is {}.'. format (type (weekend_list)))``weekend_list is .` | | ② | `>>> country_list = ['US', 'England', 'Germany', 'France']``>>> country_tuple = tuple (country_list)``>>> type (country_list)````>>> type (country_tuple)``` | #### 说明 | ① | 这是一个将元组转换为列表的简单示例。您可以使用`type ()`功能检查数据类型。 | | ② | 同样,这是一个将列表转换为元组的例子。 | ### 练习 2-56:在元组中使用 for 循环 | ① | `>>> countries = ('US', 'England', 'Germany', 'France')``>>> for country in countries:``...     print(country, end=" ")``...``US England Germany France` | #### 说明 | ① | 像列表一样,可以通过使用 for 循环来索引元组中的项。元组和列表有很多共同的属性。区别在于,一个是不可变的(tuple),一个是可变的(list),在索引过程中,一个更快(tuple),一个更慢(list)。 | ### 练习 2-57:给一个元组分配多个变量 | ① | `>>> weekend = ('Saturday', 'Sunday')``>>> (saturn, sun) = weekend``>>> print(saturn)``Saturday``>>> weekdays = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday')``>>> (moon, tiu, woden, thor, freya) = weekdays``>>> print(thor)``Thursday` | | ② | `>>> country_info = ['England', '+44']``>>> (country, code) = country_info``>>> print(country)``England``>>> print(code)``+44` | #### 说明 | ① | 您可以将多个变量分配给元组中的项。此示例使用一周七天以及各天名称的含义。星期日和星期一以太阳和月亮命名,其他日子以神话中的神命名。 | | ② | 您也可以将列表用作元组中的项目。 | ### 练习 2-58:创建一个简单的元组函数 | ① | `>>> def high_and_low (numbers):``...     highest = max (numbers)``...     lowest = min (numbers)``...     return (highest, lowest)``...``>>> lotto_numbers = [1, 37, 25, 48, 15, 23]``>>> (highest, lowest) = high_and_low (lotto_numbers)``>>> print(highest)``48``>>> print(lowest)``1` | #### 说明 | ① | 你可以做一个简单的函数,用一个 tuple 来分页最大的数或者最小的数。花几分钟时间了解该功能的工作原理,然后进入下一个练习。 | ### 练习 2-59:使用元组作为列表元素 | ① | `>>> country_code = [('England', '+44'), ('France', '+33')]``>>> for (country, code) in country_code:``...     print(country, code)``...``England +44``France +33``>>> for (country, code) in country_code:``...     print(country, end=" ")``...``England France``>>> for (country, code) in country_code:``...     print(code, end=" ")``...``+44 +33` | #### 说明 | ① | 您可以将元组用作列表中的元素。您可以选择调出每个元组中的单个项目。输入一些对你有意义的东西来获得更多的练习。 | ### 重述:元组 以下是元组的概述: * 元组有时被称为*不可变列表*,一旦创建,就不能以相同的元组形式改变。您需要首先将元组转换回列表,然后可以更改元素。通用元组语法如下: ```py Tuple_name = (element_1, element_2, ..., element_n) ``` * 但是,您可以在 Python 中删除整个元组。 * 您可以使用内置函数`list()`将元组转换为列表。 * 您可以使用内置函数`tuple()`将列表转换为元组。 * 您可以使用`max()`和`min()`方法找到一个元组的最大值和最小值。 ## 字典 让我们来谈谈字典。 ### 练习 2-60:理解词典基础知识 | ① | `>>> fav_activity = {'hugh': 'computer games', 'leah': 'ballet', 'caitlin': 'ice skating'}``>>> print(fav_activity)``{'hugh': 'computer games', 'leah': 'ballet', 'caitlin': 'ice skating'}` | | ② | `>>> fav_activity = {'hugh': 'computer games', 'leah': 'ballet', 'caitlin': 'ice skating'}``>>> print(fav_activity ['caitlin'])``ice skating` | | ③ | `>>> fav_activity = {'hugh': 'computer games', 'leah': 'ballet', 'caitlin': 'ice skating'}``>>> del fav_activity ['hugh']``>>> print(fav_activity)``{'leah': 'ballet', 'caitlin': 'ice skating'}` | | ④ | `>>> print(fav_activity)``{'leah': 'ballet', 'caitlin': 'ice skating'}``>>> fav_activity ['leah'] = 'swimming'``>>> print(fav_activity)``{'leah': 'swimming', 'caitlin': 'ice skating'}` | #### 说明 | ① | 创建一个名为`fav_activity`的字典。把家人最喜欢的爱好做成字典。和真实的字典一样,字典通常有键和值元素。一个 Python 字典键:值元素被包装在一个花括号集`{ }`中,用逗号分隔。 | | ② | 你可以用一个名为`caitlin`的键调出这个键的值。 | | ③ | 使用`del`语句删除键`hugh`。当您删除一个键时,它的值也会被删除。现在你知道 Python 中的字典是一个可变对象。 | | ④ | 将字典中键`leah`的值改为`swimming`。 | ### 练习 2-61:避免字典类型错误,将两个列表转换为一个字典 | ① | `>>> fav_activity = {'leah': 'swimming', 'caitlin': 'ice skating'}``>>> fav_subject = {'leah': 'math', 'caitlin': 'english'}``>>> print(fav_activity + fav_subject)``Traceback (most recent call last):``File "", line 1, in ``TypeError: unsupported operand type (s) for +: 'dict' and 'dict'` | | ② | `>>> keys = ['a', 'b', 'c', 'm', 'p']``>>> values = ['apple', 'banana', 'coconut', 'melon', 'pear']``>>> fruits = dict (zip (keys, values))``>>> fruits``{'a': 'apple', 'b': 'banana', 'c': 'coconut', 'm': 'melon', 'p': 'pear'}` | #### 说明 | ① | 在 Python 中,不能将两个或多个字典链接成一个字典。试图将两个字典合并成一个将返回`TypeError: unsupported operand`。 | | ② | 但是,如果一个列表只包含键,而另一个列表包含相同数量的对应值,那么这两个列表可以连接起来形成一个字典。在本例中,您刚刚使用一个字母列表(键)和另一个包含相应水果名称(值)的列表创建了一个名为 fruits 的字典。 | ### 练习 2-62:使用键打印字典中的值 | ① | `>>> dialing_code = {'France': '+ 33', 'Italy': '+ 39', 'Spain': '+ 34', 'England': '+ 44'}``>>> France_code = dialing_code ['France']``>>> Italy_code = dialing_code ['Italy']``>>> Spain_code = dialing_code ['Spain']``>>> England_code = dialing_code ['England']``>>> print('Press {} first to call France.'. format (France_code))``Press +33 first to call France.``>>> print('Press {} first to call Italy.'. format (Italy_code))``Press +39 first to call Italy.``>>> print('Press {} first to call Spain.'. format (Spain_code))``Press +34 first to call Spain` `.``>>> print('Press {} first to call England.'. format (England_code))``Press +44 first to call England.` | #### 说明 | ① | 在前面的字典示例中,您使用键来调用相应的值,并将它们打印在计算机显示器上。试着练习所有的例子。很多新手程序员问同样的问题,如何在编码方面做得更好;实践是不可替代的。 | ### 练习 2-63:更改字典值 | ① | `>>> dialing_code = {'France': '+ 33', 'Italy': '+ 39', 'Spain': '+ 34', 'England': '+ 44'}``>>> dialing_code ['England'] = '+ 44-20'``>>> England_London = dialing_code ['England']``>>> print('Dial {} to call London, England.'. format (England_London))``Dial + 44-20 to call London, England.` | #### 说明 | ① | 与 Python 中的其他可变对象一样,您也可以更改字典。在本练习中,您已经更新了键`England`的值。您刚刚在国家代码+44 后面加上了伦敦的区号 20。然后使用`format()`函数打印出一个简单的语句。 | ### 练习 2-64:向字典中添加一组新的键和值 | ① | `>>> dialing_code = {'France': '+ 33', 'Italy': '+ 39', 'Spain': '+ 34', 'England': '+ 44'}``>>> dialing_code ['Greece'] = '+30'``>>> dialing_code``{'England': '+44', 'Greece': '+30', 'Italy': '+39', 'Spain': '+34', 'France': '+33'}` | #### 说明 | ① | 您可以随意添加其他键和值对。字典和列表一样,是可变的容器。注意这个例子中字典的无序方式。Python 字典中不需要数字索引,因为键用于调用值。 | ### 练习 2-65:找出字典元素的数量 | ① | `>>> dialing_code = {'England': '+44', 'Greece': '+30', 'Italy': '+39', 'Spain': '+34', 'France': '+ 33 '}``>>> print(len (dialing_code))``5` | #### 说明 | ① | 使用`len()`查找字典中`key:value`对的数量。 | ### 练习 2-66:删除字典键和值 | ① | `>>> dialing_code = {'England': '+44', 'Greece': '+30', 'Italy': '+39', 'Spain': '+34', 'France': '+ 33 '}``>>> del dialing_code ['Italy']``>>> print(dialing_code)``{'England': '+44', 'Greece': '+30', 'Spain': '+34', 'France': '+33'}` | #### 说明 | ① | 要删除一个键,使用字典中的值对;使用`"del"`键。该值将与密钥一起自动删除。 | ### 练习 2-67:用字典编写 Python 脚本 | ① | `# ex2_67.py``dialing_code = {'England': '+44', 'Greece': '+30', 'Italy': '+39', 'Spain': '+34', 'France': '+33'}``for code in dialing_code` `:``print('The country code for {0} is {1}.'. format (code, dialing_code [code]))``# Output:``The country code for England is +44.``The country code for Greece is +30.``The country code for Italy is +39.``The country code for Spain is +34.``The country code for France is +33.` | Note 尝试在 Notepad++上保存`ex2_67.py`脚本,用相同的名称保存文件,使用 Ctrl+F6 键运行代码,如图 2-6 所示。 ![img/492721_1_En_2_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Fig6_HTML.jpg) 图 2-6。 记事本++字典示例 #### 说明 | ① | 现在让我们熟悉如何制作这样的简单脚本,并在 IDLE 或 Notepad++上运行它。在这个例子中,您已经使用了 format 函数来调用键和值对,并让它们通过一个`for`循环来打印出每个国家的国际拨号代码。 | ### 练习 2-68:使用字典进行循环和格式化 | ① | `>>> countries = {'England': {'code':'+ 44', 'capital_city':'London'}, 'France': {'code':'+ 33', 'capital_city':'Paris'}}``>>> for country in countries` `:``...     print("{} 's country info:" .format (country))``...     print(countries [country] ['code'])``...     print(countries [country] ['capital_city'])``...``England 's country info:``+ 44``London``France 's country info:``+ 33``Paris` | | ② | `>> countries = {'England': {'code':'+ 44', 'capital_city':'London'}, 'France': {'code':'+ 33', 'capital_city':'Paris'}}``>>> for country in countries:``...     print(f"{country}'s country info:")``...     print(countries [country] ['code'])``...     print(countries [country] ['capital_city'])``...``England's country info:``+ 44``London``France's country info:``+ 33``Paris` | #### 说明 | ① | 您可以在字典中嵌套字典。使用一个`for`循环来打印字典的元素`countries`。 | | ② | 例①中的`print("{} 's country info:" .format (country))`与例②中的`print(f"{country}'s country info:")`相同。第二个版本是简化版,帮助我们编写更短的代码。 | ### 总结:字典 这里是一个回顾: * 一个字典由`key:value`对集合组成,这些集合由一个逗号分隔符分隔,逗号分隔符用大括号`{ }`括起来。常规字典语法如下: * 要调用存储在字典中的值,必须使用键而不是索引。字典不使用数字索引,所以它是一个无序的序列集合。 * 您可以使用关键字作为索引来更改关键字的值。下面举个例子:`dictionary_name ['key_1'] = 'new_value_1'`。 * 使用`del`删除键会删除键和值。这里有一个例子:del dictionary_name ['key_1']。 * `dictionary_name.values()`中的`value_x`会告诉你一个键是否是字典的一部分。 * `dictionary_name.keys()`只返回字典的关键字。 * `dictionary_name.values()`只返回字典的值。 * 您可以在字典上使用`for`循环,就像在列表和元组中一样。 * 字典可以有任何数据类型作为元素,只要它们有正确的`key: value`配对。 ```py dictionary_Name = ['Key_1': 'Value_1', 'Key_2': 'Value_2', 'Key_n': 'Value_n'] ``` ## 处理文件 Python 中的文件处理技能是基础,因为业务关键数据必须使用各种文件处理模块和方法来处理。最简单的文件处理方式是读写文本文件。随着您熟悉更复杂的文件处理,您可以使用 Python 模块学习更高级的文件处理方法,例如用于数据处理的 Pandas 和 NumPy,或用于 Excel 文件的 xlrd 和 openpyxl。但是,即使在熟悉这些模块之前,也要掌握如何使用索引、切片或使用 regex 处理数据来进行基本的数据处理。有一本关于如何使用 Python Pandas 模块的 500 页的书,因此在一节中涵盖数据处理和文件处理的所有主题是一项不值得羡慕的任务。 然而,这本书旨在让你接触各种基本的数据处理练习和文件处理方法。您将熟悉 Python 如何处理数据,然后可以通过基本的文件处理方法来处理处理后的数据。 ### 练习 2-69:从 PC 上读取并显示 Hosts 文件 从 Microsoft Windows Python 交互式 shell 中运行此程序。 | ① | `>>> hosts = open('c://windows//system32//drivers//etc//hosts')``>>> hosts_file = hosts.read()``>>> print(hosts_file)``# Copyright (c) 1993-2009 Microsoft Corp. #``# This is a sample HOSTS file used by Microsoft TCP/IP for Windows. #``# This file contains the mappings of IP addresses to host names. Each # entry should be kept on an individual line. The IP address should``# be placed in the first column followed by the corresponding host name. # The IP address and the host name should be separated by at least one # space` `.``#``# Additionally, comments (such as these) may be inserted on individual # lines or following the machine name denoted by a '#' symbol.``#``# For example:``#``#   102.54.94.97``rhino.acme.com``# source server #    38.25.63.10``x.acme.com``# localhost name resolution is handled within DNS itself. #   127.0.0.1   localhost``#   ::1   localhost` | 或者,从 Linux 或 macOS Python 交互式 shell 中运行: | ② | `>>> hosts = open ('/etc/hosts')``>>> hosts_file = hosts.read()``>>> print(hosts_file)``127.0.0.1 localhost.localdomain localhost``:: 1 localhost6.localdomain6 localhost6``The following lines are desirable for IPv6 capable hosts``:: 1 localhost ip6-localhost ip6-loopback fe00 :: 0 ip6-localnet``ff02 :: 1 ip6-allnodes ff02 :: 2 ip6-allrouters``ff02 :: 3 ip6-allhosts` | #### 说明 | ① | 您刚刚从 Windows 计算机上读取了 hosts 文件,并将其打印在计算机屏幕上。读取一个文件就是这么简单,如果你没有指定读取方法,它通常以`r`(读取)模式打开。请注意在打开主机文件时,Windows OS 文件系统中的每个`\`是如何被替换为`//`的。如果您坚持在 Windows 上运行 Python,并且必须处理文件,那么这是从这个例子中得出的重要观点。 | | ② | 您可以在 Linux 或 macOS 系统上使用相同的代码来读取 hosts 文件(对象),但是这次 hosts 文件存在于`/etc/hosts`下。在 Python 中,打开、读取和打印文件内容只需要三行代码。 | ### 练习 2-70:打开和关闭 Hosts 文件 | ① | `>>> hosts = open('c://windows//system32//drivers//etc//hosts')``>>> hosts_file = hosts.read()``>>> print(hosts_file)``# Copyright (c) 1993-2009 Microsoft Corp` `.``.``[... omitted for brevity]``.``# localhost name resolution is handled within DNS itself.``# 127.0.0.1 localhost``# ::1 localhost``>>> hosts.close ()` | #### 说明 | ① | 用`open('file_path')`方法打开文件后,必须在使用后关闭文件。这个过程与您在计算机上打开文本或 word 文件是一样的,使用后,您必须关闭该文件。您可以使用`file_variable.close()`方法关闭文件。在我们的例子中,使用`hosts.close()`关闭打开的文件。 | ### 练习 2-71:创建代码以两种方式关闭文件 | ① | `>>> hosts = open ('c://windows//system32//drivers//etc//hosts', 'r')``>>> hosts_file_contents = hosts.read()``>>> print('File closed? {}'.format(hosts.closed))``File closed? False``>>>``>>> if not hosts.closed:``...     hosts.close()``...``>>>``>>> print('File closed? {}'.format(hosts.closed))``File closed? True``>>>` | | ② | `>>> with open ('c://windows//system32//drivers//etc//hosts', 'r') as hosts:``...     print('File closed? {}'.format (hosts.closed))``...     print(hosts.read())``...     print('Finished reading the file.')``...``File closed? False``# Copyright (c) 1993-2009 Microsoft Corp.``[... omitted for brevity]``# localhost name resolution is handled within DNS itself.``# 127.0.0.1 localhost``# ::1 localhost``Finished reading the file.``>>>``>>> print('File closed? {}'. format (hosts.closed))``File closed? True``>>>` | | ③ | `try:``f = open('c://windows//system32//drivers//etc//hosts', encoding = 'utf-8')``f_read = f.read()``print(f_read)``finally:``f.close()` | #### 说明 | ① | 键入简单的 Python 脚本来打开 hosts 文件,然后关闭它。然后检查文件是否已正确关闭。True 表示文件已正确关闭。有时这个文件打开选项会派上用场。 | | ② | 当您使用`open()`选项时,这消除了添加`close()`行的需要。`with open`语句在没有`close()`语句的情况下自动打开和关闭文件。尝试使用此选项打开文件,而不是使用`file.open()`方法。 | | ③ | 如果坚持使用 file `open()`方法,在一个`try-finally`块中打开文件,这样如果某个文件操作失败突然关闭,它就正确关闭文件。另外,请注意,编码被指定为 UTF-8。在 Python 3.4+上,这已经是默认的编码方法,但在本例中仅用于演示目的。 | ### 练习 2-72:创建一个文本文件,并进行读取、写入和打印 | ① | `>>> f = open('C://Users//brendan//Documents//file1.txt', 'w+')` `# Replace my name with yours``>>> for i in range(3):``...     f.write('This is line %d.\r' %(i + 1))``...``16``16``16``>>> f.close() #` `Always close file. Data gets written as file closes.``>>>``>>> f = open('C://Users//brendan//Documents//file1.txt')``>>> f_read = f.read()``>>> print(f_read)``This is line 1\. This is line 2\. This is line 3.`◆t0]`>>>``>>> print(f_read.strip())#` `Removes undesired whitespaces.``This is line 1\. This is line 2\. This is line 3.``>>>``>>> print(f_read, end='')` `# Removes undesired whitespaces.``This is line 1\. This is line 2\. This is line 3.``>>>``>>> f.close()` `# Always close file.``>>>` | | ② | `>>> with open('C://Users//brendan//Documents//file1.txt', 'w+') as f:``...     for i in range(3):``...     f.write('This is line %d.\n' %(i + 1))``...``16``16``16``>>> with open('C://Users//brendan//Documents//file1.txt', 'r') as f:``...     for line in f:``...     print(line)`...`This is line 1.`◆t0]`This is line 2.`◆t0]`This is line 3.`◆t0] | | ③ | `>>> with open('C://Users//brendan//Documents//file1.txt', 'r') as f:``...     for line in f:``...     print(line, end=' ')``...``This is line 1\. This is line 2\. This is line 3.` | | ④ | `>>> with open('C://Users//brendan//Documents//file1.txt', 'r') as f:``...     for line in f:``...     print(line.strip())``...``This is line 1\. This is line 2\. This is line 3.` | | ⑤号 | `>>> with open('C://Users//brendan//Documents//file1.txt', 'r') as f:``...     skip_header = next(f) #Removes the header or the first line.``...     for line in f:``...         print(line.strip())``...``This is line 2\. This is line 3.` | | ⑥ | `>>> with open('C://Users//brendan//Documents//file1.txt', 'w+') as f:``...     for i in range(3):``...         f.write('This is line %d.\r\n' %(i + 1))``...``17``17``17``>>> with open('C://Users//brendan//Documents//file1.txt', 'r') as f:``...     for line in f:``...         print(line)``...``This is line 1.`◆t0]▯▯`This is line 2.`◆t0]▯▯`This is line 3.`◆t0]▯▯ | #### 说明 | ① | 这是一个以写模式(`w+`)打开(创建)文件并写入文件的基本例子。信息写入后,文件关闭。然后在阅读(`r`)模式下再次打开文件,文件内容在屏幕上打印出来。每次使用后都必须关闭文件。使用`strip()`或`end=''`删除文件写入过程中产生的任何空白。如果需要,您可以删除不需要的空白。 | | ② | 在第二个例子中,您已经使用了`open with`方法来创建和读取文件,所以没有必要关闭文件。此外,当您创建文件时,您使用了`\n`(换行)而不是`\r`(回车),当内容被读取时,您可以看到新行已经被添加。空白包括`\t`(制表符)、`\n`(换行符)、以及`\r`(回车符)。 | | ③ | 您再次使用了`end='`来删除不需要的空白。 | | ④ | 同样,使用`strip()`删除任何空白。 | | ⑤号 | `skip_header = next(f)`可以从第二行删除文件头或第一行来打印。 | | ⑥ | 小心同时使用`\r`和`\n`,因为最终产品的行为会因操作系统的不同而不同。如果在 Linux 或 macOS 中处理文件,`\r`和`\n`的用法会有所不同。 | ### 练习 2-73:使用 rstrip()或 lstrip()删除空白 | ① | `>>> with open('C://Users//brendan//Documents//file1.txt', 'w+') as f:``...     for i in range(3):``...     f.write('``This line contains whitespaces %d.``\n' %(i + 1))``...``42``42``42``>>> with open('C://Users//brendan//Documents//file1.txt', 'r') as f:``...     for line in f:``...     print(line)``...`■t0〖t1〗□□□□□□□□□□□□□□□□□◆t0]§t0§▯§t0§▯ | | ② | `>>> with open('C://Users//brendan//Documents//file1.txt', 'r') as f:``...     for line in f:``...     print(line.lstrip())``...``This line contains whitespaces 1.`№0□▯`This line contains whitespaces 2.`№0□▯`This line contains whitespaces 3.`№0□▯ | | ③ | `>>> with open('C://Users//brendan//Documents//file1.txt', 'r') as f:``...     for line in f:``...         print(line.rstrip())``...`■t0]■t0]■t0] | | ④ | `>>> with open('C://Users//brendan//Documents//file1.txt', 'r') as f:``...     for line in f:``...         print(line.strip())``...``This line contains whitespaces 1\. This line contains whitespaces 2\. This line contains whitespaces 3.` | #### 说明 | ① | 创建一个左边四个空格,右边四个空格的文件。 | | ② | 使用`lstrip()`删除左侧的空白。 | | ③ | 使用`rstrip()`删除右边的空格,包括\n 换行符。 | | ④ | 使用`strip()`删除所有空白。 | ### 练习 2-74: Python 文件模式练习:使用 r 模式 当你在读(r)模式下打开一个文件时,文件指针被放在文件的开头,这是默认模式。 | ①#1 | `>>> with open('C://Users//brendan//Documents//file2.txt', 'r') as f:``...     print('Created file2.txt')``...``Traceback (most recent call last):``File "", line 1, in ``FileNotFoundError: [Errno 2] No such file or directory: 'C://Users//brendan//Documents//file2.txt'``>>> import os` | | #2 | `>>> os.path.isfile('C://Users//brendan//Documents//file2.txt')``False` | | #3 | `>>> with open('C://Users//brendan//Documents//file2.txt', 'w') as f:``...     print('Created file2.txt')``...` | | #4 | `Created file2.txt``>>> import glob``>>> print(glob.glob('C://Users//brendan//Documents//*.txt'))` | | #5 | `['C://Users//brendan//Documents\\file1.txt', 'C://Users//brendan//Documents\\file2.txt']``>>> import os``>>> os.path.isfile('C://Users//brendan//Documents//file2.txt')` | | #6 | `True` | | #7 | `>>> with open('C://Users//brendan//Documents//file2.txt') as f:``...     print(f.mode)``...``r #``If file mode is not specified, the``>>> with open('C://Users//brendan//Documents//file2.txt') as f:``...     f.write ('Writing to a file is fun.')``...``Traceback (most recent call last):``File "", line 2, in ``io.UnsupportedOperation: not writable` | #### 说明 | ① | #1.您试图以默认读取模式创建文件,但 Python 不允许以只读(r)模式创建文件。#2.您导入了 os 模块,并使用了`os.path.isfile()`方法来检查是否创建了一个文件,但是正如所料,没有创建任何文件。#3.现在您已经以写(w)模式打开并创建了`file2.txt`。#4.然后您导入 globe 模块以确认文件存在于您的文档文件夹中。#5.使用`os.path.isfile()`方法,您仔细检查了文件夹(Linux 中的目录)中创建并存在的文件。#6.您已经使用了`.mode`来验证文件模式。#7.你已经在读取模式下打开了`file2.txt`并试图写入文件,Python 提醒你在这种情况下不能写入文件。 | ### 练习 2-75: Python 文件模式练习:使用 r+模式 打开文件进行读写。文件指针将位于文件的开头。 | ① |   | | #1 | `>>> import os``>>> os.remove('C://Users//brendan//Documents//file2.txt')``>>> os.path.isfile('C://Users//brendan//Documents//file2.txt')` | | #2 | `False``>>> with open('C://Users//brendan//Documents//file2.txt', 'r+') as f:``...     f.write('* Test Line 1')``...     print('Trying to write the first line.')``...``Traceback (most recent call last):``File "", line 1, in ` | | #3 | `FileNotFoundError: [Errno 2] No such file or directory: 'C://Users//brendan//Documents//file2.txt'``>>> with open('C://Users//brendan//Documents//file2.txt', 'w+') as f:``...     f.write('* Test Line 1')``...     print('Just created file2.txt with line 1')``...``13``Just created file2.txt with line 1``>>> with open('C://Users//brendan//Documents//file2.txt', 'r+') as f:` | | #4 | `...     print(f.mode)``...``r+ # Opens a file for both reading and writing.` | | #5 | `>>> with open('C://Users//brendan//Documents//file2.txt', 'r+') as f:``...     f_read = f.read()``...     print(f_read)``...` | | #6 | `* Test Line 1``>>> with open('C://Users//brendan//Documents//file2.txt', 'r+') as f:``...     f.write('# This will overwrite Line 1')``...``28``>>> with open('C://Users//brendan//Documents//file2.txt', 'r+') as f:``...     f_read = f.read()``...     print(f_read)``...``# This will overwrite Line 1` | #### 说明 | ① | #1.使用`os.remove()`方法删除现有文件 file2.txt。该文件是在练习 2-74 中创建的。您还可以使用`os.path.isfile()`方法检查文件是否已被成功删除。#2.尝试在`r+`模式下打开并创建一个文件;就像在 r 模式下,Python 会返回`FileNotFoundError`。您不能使用 r+模式创建新文件。#3.这次使用`w+`模式重新创建 file2.txt 文件,并写入第一行进行测试。如练习 2-74 中一样,`w+`允许你打开并创建一个新文件;在下面的`w`和`w+`练习中,你会找到更多关于这方面的内容。#4.使用`.mode`方法检查您的文件处理模式。#5.现在在`r+`模式下重新打开`text2.txt`并写入文件。#6.当您读取该文件时,可以看到最后一个操作已经覆盖了旧的信息。 | ### 练习 2-76: Python 文件模式练习:使用模式 打开一个文件做附加练习。如果文件存在,文件指针在文件的末尾。文件处于追加模式。如果该文件不存在,它将创建一个新文件进行写入。 | ① |   | | #1 | `>>> import os``>>> os.remove('C://Users//brendan//Documents//file2.txt')``>>> os.path.isfile('C://Users//brendan//Documents//file2.txt')``False``>>> with open('C://Users//brendan//Documents//file2.txt', 'a') as f:``...     f.write('This is line 1.')``...``15` | | #2 | `>>> with open('C://Users//brendan//Documents//file2.txt', 'a') as f:``...     f_read = f.read()``...     print(f_read)``...``Traceback (most recent call last):` | | #3 | `File "", line 2, in ``io.UnsupportedOperation: not readable``>>> with open('C://Users//brendan//Documents//file2.txt', 'r') as f:``...     f_read = f.read()` | | #4 | `...     print(f_read)``...` | | #5 | `This is line 1.``>>> with open('C://Users//brendan//Documents//file2.txt', 'a') as f:``...     f.write('This is line 2.')``...` | | #6 | `15``>>> with open('C://Users//brendan//Documents//file2.txt', 'r') as f:``...     f_read = f.read()``...     print(f_read)``...``This is line 1.This is line 2.` | | #7 | `>>> with open('C://Users//brendan//Documents//file2.txt', 'a') as f:``...     f.write('\rThis is line 3.')``...``16``>>> with open('C://Users//brendan//Documents//file2.txt', 'r') as f:``...     f_read = f.read()``...     print(f_read)` | | #8 | `...``This is line 1.This is line 2\. This is line 3.``>>> with open('C://Users//brendan//Documents//file2.txt', 'a') as f:``...``This is line 1.This is line 2\. This is line 3.``>>> with open('C://Users//brendan//Documents//file2.txt', 'a') as f:` | | #9 | `...     f.write('\nThis is line 4.')``...``16``>> with open('C://Users//brendan//Documents//file2.txt', 'r') as f:``...     for line in f.readlines():` | | #10 | `...         print(line.strip())``...``This is line 1.This is line 2\. This is line 3.` | | #11 | `This is line 4.``>>> with open('C://Users//brendan//Documents//file2.txt', 'r') as f:``...      f.readline()``...` | | #12 | `'This is line 1.This is line 2.\n'``>>> with open('C://Users//brendan//Documents//file2.txt', 'r') as f:``...     f.readlines()``...``['This is line 1.This is line 2.\n', 'This is line 3.\n', 'This is line 4.']``>>> with open('C://Users//brendan//Documents//file2.txt', 'r') as f:``...     f.tell()``...``0``>> with open('C://Users//brendan//Documents//file2.txt', 'a') as f:``...     f.tell()``...``63` | #### 说明 | ① | #1.移除`file2.txt`并以(追加)模式重新创建一个同名文件。正如您已经注意到的,追加模式还允许您创建一个新文件,如果它还不存在的话。#2.在追加模式下打开`file2.txt`读取文件。Python 会告诉你,在这种模式下,你无法读取文件。#3.以阅读模式打开文件,并将内容打印到屏幕上。#4.这一次,以追加模式打开文件,并添加更多信息。#5.再次以阅读模式打开文件并打印文件内容。正如您已经注意到的,您写入文件的第二句话被附加在第一句话之后,没有适当的间距或换行符。在追加模式下,指针从最后一句的末尾开始,在最后一个字符条目的末尾追加任何附加信息。#6.在追加模式下打开`file2.txt`并添加一个新行。这一次,添加`\r`将该行添加到下一行。#7.在阅读模式下确认输入。#8.这一次,使用`\n`添加另一行。#9.当你使用`strip()`方法时,像`\r`或`\n`这样的空白会被删除。#10.当您使用`read()`方法时,Python 读取整个文件内容并转储信息。有时这并不是您想要的方法,因为您只想逐行读取。使用`readline()`一次读取一行。#11.如果使用`readlines()`方法,Python 将读取每一行,并将它们作为字符串项列表返回。#12.Python 的 file 方法`tell()`返回文件中文件的当前位置读/写指针。正如您所观察到的,当一个文件以 r/r+模式打开时,指针从索引 0 开始。而如果您以 a/a+模式打开文件,指针将从索引的末尾开始。 | ### 练习 2-77: Python 文件模式练习:使用 a+模式 打开一个文件进行附加和阅读练习。如果文件存在,文件指针在文件的末尾。文件以追加模式打开。如果文件不存在,它会创建一个新文件进行读写。 | ①#1#2#3 | `>>> import os``>>> os.remove('C://Users//brendan//Documents//file2.txt') # Removes old file2.txt``>>> with open('C://Users//brendan//Documents//file2.txt', 'a+') as f:``...     print(f.mode)``...     f.write('This is line 1.\rThis is line 2.\rThis is line 3.\r')``...``a+``48``>>> with open('C://Users//brendan//Documents//file2.txt', 'a+') as f:``...     f_read = f.read()``...     print(f_read, end='')``...``>>> with open('C://Users//brendan//Documents//file2.txt', 'r') as f:``...     f_read = f.read()``...     print(f_read, end='')``...``This is line 1\. This is line 2\. This is line3.` | #### 说明 | ① | #1.再次删除`file2.txt`,在 a+模式下用三行打开并重新创建`file2.txt`文件。#2.在`a+`模式下打开文件,读取并打印文件。但是由于在`a+`模式下指针位于文件的末尾,所以不会打印任何信息。#3.当您再次以默认的`r`模式打开文件时,指针从文件的开头开始,并在屏幕上打印出文件内容。虽然`a+`支持文件读取模式,但是由于在这种模式下打开文件时的指针位置,实际上在这种模式下打开文件并不公平。 | ### 练习 2-78: Python 文件模式练习:使用 w 模式 打开一个只写的文件。如果文件存在,这将覆盖该文件。如果该文件不存在,请创建一个新文件进行写入。 | ①#1#2#3 | `>>> with open('C://Users//brendan//Documents//file2.txt', 'w') as f:``...     f.tell()``...     print(f.mode)``...     f.write('This is line 1.')``...``0``w``15``>>> with open('C://Users//brendan//Documents//file2.txt', 'r') as f:``...     f_read = f.read()``...     print(f_read)``...``This is line 1.``>>> with open('C://Users//brendan//Documents//file2.txt', 'w') as f:``...     f_read = f.read()``...     print(f_read)``...``Traceback (most recent call last): File "", line 2, in ``io.UnsupportedOperation: not readable` | #### 说明 | ① | #1.没有必要删除旧文件。以 w 模式打开文件将覆盖旧文件并创建一个新文件。如果没有同名的文件,Python 将创建一个新文件。#2.您以读取模式打开了`file2.txt`,并以 r 模式打印了文件内容。#3.如果您尝试在`w`模式下读取文件内容,您将遇到一个与 IO 相关的错误,如下例所示。 | ### 练习 2-79: Python 文件模式练习:使用 w+模式 打开文件进行读写。如果文件存在,这将覆盖现有文件。如果该文件不存在,请创建一个新文件进行读写。 | ①#1#2 | `>>> with open('C://Users//brendan//Documents//file2.txt', 'w+') as f:``...     f.tell()``...     f.write('This is line 1.')``...     f.tell()``...     print(f.read())``...``0``15``15``>>> with open('C://Users//brendan//Documents//file2.txt', 'w+') as f:``...     print(f.mode)``...     f.write('This is line 1.')``...     f.seek(0)``...     print(f.read())``...``w+``15``0``This is line 1.` | #### 说明 | ① | #1.当您在`w+`模式下打开时,在写入文件后,指针从索引 0 开始。它移动到内容的末尾。当指针位于行尾时,如果您试图打印信息,Python 将返回一个空白。#2.要在`w+`模式下写入,然后读取并打印出文件内容,则必须使用`seek(0)`方法将指针移动到零位置。在本例中,您已经将指针位置重置为 0,即文件的开头,Python 正确地打印了这些行。 | ### 练习 2-80: Python 文件模式练习:使用 x 模式 x 就像 w,但是对于 x,如果文件存在,就提高`FileExistsError`。 | ①#1#2 | `>>> with open('C://Users//brendan//Documents//file2.txt', 'x') as f:``...     print(f.mode)``...``Traceback (most recent call last):``File "", line 1, in ``FileExistsError: [Errno 17] File exists: 'C://Users//brendan//Documents//file2.txt'``>>> with open('C://Users//brendan//Documents//file4.txt', 'x') as f:``...     print(f.mode)``...``X` | #### 说明 | ① | #1.`file2.txt`在之前的练习中已经存在。当你试图在`x`模式下打开`file2.txt`时,它会升起`FileExistsError`。如果您不想错误地使用`w` / `w+`模式覆盖现有文件,这种新的文件处理模式可以派上用场。#2.现在在 x 模式下创建一个新文件,Python 对这个操作很满意。 | ### 练习 2-81: Python 文件模式练习:使用 x 模式 `x`只可写。`x+`会写会读。 | ① | `>>> with open('C://Users//brendan//Documents//file5.txt', 'x+') as f:``...     print(f.mode)``...``x+` | #### 说明 | ① | 这是 x 模式的一种读写模式。它几乎与 w+模式相同,但它不会覆盖现有的文件。创建新文件需要排他性。所有文件模式见表 2-5;您现在不必记住所有的模式,但是随着您编写更多的 Python 代码,您将会熟悉每种模式。 | 表 2-5。 文件处理模式 | 方式 | 描述 | | --- | --- | | `r` | 以只读方式打开文件。这种模式将文件指针放在文件的开头。这是默认模式。 | | `rb` | 以二进制格式打开只读文件。这种模式将文件指针放在文件的开头。 | | `r+` | 打开文件进行读写。文件指针将位于文件的开头。 | | `rb+` | 以二进制格式打开文件进行读写。文件指针将位于文件的开头。 | | `a` | 打开要追加的文件。如果文件存在,文件指针在文件的末尾。文件处于追加模式。如果该文件不存在,它将创建一个新文件进行写入。 | | `ab` | 以二进制格式打开要追加的文件。如果文件存在,文件指针在文件的末尾。文件处于追加模式。如果该文件不存在,它将创建一个新文件进行写入。 | | `a+` | 打开文件进行追加和读取。如果文件存在,文件指针在文件的末尾。文件以追加模式打开。如果文件不存在,它会创建一个新文件进行读写。 | | `ab+` | 以二进制格式打开文件进行追加和读取。如果文件存在,文件指针在文件的末尾。文件以追加模式打开。如果文件不存在,它会创建一个新文件进行读写。 | | `w` | 打开一个只写的文件。如果文件存在,则覆盖文件。如果文件不存在,则创建一个新文件进行写入。 | | `wb` | 以二进制格式打开一个只写的文件。如果文件存在,则覆盖文件。如果该文件不存在,请创建一个新文件进行写入。 | | `w+` | 打开文件进行读写。如果文件存在,则覆盖现有文件。如果文件不存在,则创建一个新文件进行读写。 | | `wb+` | 以二进制格式打开文件进行读写。如果文件存在,则覆盖现有文件。如果该文件不存在,请创建一个新文件进行读写。 | | `x` | `x`模式类似于`w`模式。但是对于`x`,如果该文件存在,则引发`FileExistsError`。 | | `x+` | `x`只可写。`"x+"`会写会读。 | ### 练习 2-82:用 Python 打开一个字节文件 | ① | `>>> with open('C://Users//brendan//Pictures//ex82_horse.jpg', 'rb') as horse_pic:``...     horse_pic.seek(2)``...     horse_pic.seek(4)``...     print(horse_pic.tell())``...     print(horse_pic.mode)``...``2``4``4``Rb` | #### 说明 | ① | Python 还可以打开和读取字节文件,比如图像和其他文件。此示例显示了如何打开并使用 seek 方法读取字节文件。当您在字节模式下打开时,您会在正常的 r/r+、a/a+和 w/w+模式后添加`b`。 | ![img/492721_1_En_2_Figj_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Figj_HTML.gif)你可以从 pynetauto GitHub 网站下载`ex82_horse.jpg`文件,作为章节 2 代码的一部分。这个 JPEG 文件包含在`chapter2_codes.zip`文件中。这是我的照片,所以这张照片没有版权。 URL: [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) ### 练习 2-83:用 try 和 except 处理错误 或者,您可以下载名为`ex2_83_countries.py`和`ex2_83_countries.txt`的源代码文件,然后从 Notepad++运行代码。 | ① | `>>> try:``...     countries = open('C://Users//brendan//Documents//ex2_83_countries.txt', 'r')``... except FileNotFoundError as e:``...     print(str(e))``... else:``...     nations = countries.read()``...     print(nations)``...     countries.close()``...``[Errno 2] No such file or directory: 'C://Users//brendan//Documents//ex2_83_countries.txt'` | | ② | `>>> try:``...     countries = open('C://Users//brendan//Documents//ex2_83_countries.txt', 'r')``... except FileNotFoundError as e:``...     print(str(e))``... else:``...     nations = countries.read()``...     print(nations)``...     countries.close()``...``United States England Germany France``Japan Italy Spain Australia` | #### 说明 | ① | 您可以使用`try`和`except`、`try`、`except`和`else`,或者`try`和`finally`来处理文件处理过程中出现的错误。第一个练习向您展示了一个目录中没有文件的错误示例。下载`ex83_countries.txt`文件并放到`Documents`文件夹中,然后执行下一个任务。 | | ② | 如果目录中有正确的文件,Python 代码将到达 else 语句并打印出文件中的国家名称。`try`和`except`错误处理在处理错误时变得很方便,尤其是在处理文件时。 | ### 概述:Python 文件处理概念 这里是一个回顾: * 使用`open()`内置函数打开文件。一般语法如下: * `open(file_location, mode)` * 如果未指定文件模式,文件将以默认的`r`(读取)模式打开。 * 在`read()`文件模式下,读取文件的全部内容。 * 当使用`open()`功能打开文件时,最佳做法是使用`close()`功能关闭文件。 * 如果您使用`open ~`打开文件,您不必使用`close()`功能关闭打开的文件。 * for 循环逐行读取文件内容。 * 文件处理过程中产生的空白可使用`strip()`、`rstrip()`、`lsetrip()`和`end='`方法删除。 * 您可以使用 **write()** 功能将数据写入文件。 * 文件以文本模式打开,除非使用`b`模式指定以字节模式打开。计算机喜欢用二进制格式处理数据。 * 英语字母或数字的大小被识别为 1 个字节,但 UTF-8 格式中的一些字符,如朝鲜语、中文和日语,其大小可能大于 1 个字节。 * `try`和`except`错误处理可以帮助你编写更健壮的 Python 脚本。 ## 使用 Python 模块 在 Python 中,*模块*和*包*这两个词可以互换使用,但是它们有什么不同呢?是的,它们是密切相关的,经常会让新的 Python 学习者感到困惑。它们在组织代码方面的目的是一样的,但是两者之间有一些细微的差别,因为它们都提供了稍微不同的组织代码的方式。通常,模块是具有某种功能的单个`.py` Python 文件。包是包含多个 Python 模块的目录。所以,包是模块的集合。一个模块可以被认为是一个自包含的包,而一个包是多个文件中不同模块的集合。通常,您从一个模块开始,随着需求的增长,您将多个模块变成一个包,以服务于您工作中的某个目的。 随着您更多地使用 Python 代码,您将使用许多内置模块和包,采用和使用其他人的模块和包,或者创建您自己的自定义模块和包以在您的 Python 应用中使用。让我们利用这些模块学习一些基础知识。 ## 时间模块 先说时间模块。 ### 练习 2-84:导入时间模块 | ① | `>>> import time``>>> print(time.asctime())``Sun Apr 12 00:48:07 2020` | | ② | `>> print(time.timezone)``-36000` | | ③ | `>>> from time import asctime``>>> print(asctime())``Sun Apr 12 00:49:44 2020` | #### 说明 | ① | 导入时间模块并从您的计算机打印时间。 | | ② | 使用`time.timezone`方法找到您的时区。-36000 是澳大利亚悉尼的时区值。 | | ③ | 使用 from ~ import ~仅从时间模块导入所需的函数。您可以在同一行中导入多个函数。例如,从 time,导入 gmtime 和 strftime。 | ## 睡眠方法 先说睡眠方法。 ### 练习 2-85:使用 time.sleep()函数 | ① | `#Ex2_85.py (optional)``>>> from time import asctime, sleep``>>> print(asctime())``Sun Apr 12 01:00:01 2020``>>> sleep(10)``>>> print(asctime())``Sun Apr 12 01:00:11 2020``# Output:``Sun Apr 12 01:02:05 2020``Sun Apr 12 01:02:15 2020` | #### 说明 | ① | `time.sleep()`用于让您的脚本在指定的时间内进入睡眠状态。尽可能避免在导入模块时使用`import *`(import all);仅导入您要使用的显式函数。`*`通配符意味着加载所有模块和函数,所以这会增加代码的速度,因为加载所有模块比只加载一个模块要花更多的时间。例如,尽量避免使用这个:`from time import * #` `Avoid *, as * is too greedy`这是优选的:`from time import asctime, sleep #` `importing two modules here`如果不记得模块中的函数名,可以先导入模块,用`dir(module_name)`找到所有可以选择的方法名。`>>> import time``>>> dir(time)``['CLOCK_MONOTONIC', 'CLOCK_MONOTONIC_RAW', 'CLOCK_PROCESS_CPUTIME_ID', 'CLOCK_REALTIME',``'CLOCK_THREAD_CPUTIME_ID', '_STRUCT_TM_ITEMS', ' doc ', ' loader ', ' name ', ' ' asctime ',' clock ',' clock_getres', 'clock_gettime', 'clock_settime', 'ctime', 'daylight', 'get_clock_info', 'gmtime', 'localtime', 'mktime', 'monotonic', 'perf_counter' , 'process_time', 'sleep', 'strftime', 'strptime', 'struct_time', 'time', 'timezone', 'tzname', 'tzset']` | ### 练习 2-86:使用 sys 模块浏览路径 | ① | `# For Windows``>>> import sys``>>> sys.path``['', 'C:\\Python39\\python39.zip', 'C:\\Python39\\DLLs', 'C:\\Python39\\lib', 'C:\\Python39', 'C:\\Python39\\lib\\site-packages']``# For Linux``>>> import sys``>>> sys.path``['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/ usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages']` | | ② | `# For Windows``>>> import sys``>>> for path in sys.path:``...     print(path)``...``C:\Python39\python39.zip``C:\Python39\DLLs``C:\Python39\lib``C:\Python39``C:\Python39\lib\site-packages``# For Linux``>>> import sys``>>> for path in sys.path:``...     print(path)``...``/usr/lib/python36.zip``/usr/lib/python3.6``/usr/lib/python3.6/lib-dynload``/usr/local/lib/python3.6/dist-packages``/usr/lib/python3/dist-packages` | #### 说明 | ① | 首先,导入目标模块,然后使用`sys.path`查找 Python 文件在系统中的位置。 | | ② | 为了美化输出,使用`for`循环方法逐行给出系统目录路径。在这个例子中,您可以比较`PYTHONPATH`和相关包在 Windows 和 Linux 中的安装位置。`PYTHONPATH`是一个环境变量,用于添加额外的目录,Python 将在这些目录中寻找模块和包。 | ### 练习 2-87:使用 sys 模块添加新的文件路径 | ① | `# For Windows``>>> import sys``>>> sys.path.append('C:\Python39\my-packages')``>>> for path in sys.path:``...     print(path)``...``C:\Python39\python39.zip``C:\Python39\DLLs``C:\Python39\lib``C:\Python39``C:\Python39\lib\site-packages``C:\Python38Python39\my-packages # Newly added FILEPATH``# For Linux``>>> import sys``>>> sys.path.append('/Users/root/my-packages')``>>> for path in sys.path:``...     print(path)``...``/usr/lib/python36.zip``/usr/lib/python3.6``/usr/lib/python3.6/lib-dynload``/usr/local/lib/python3.6/dist-packages``/usr/lib/python3/dist-packages``/Users/root/my-packages` `# Newly added FILEPATH` | #### 说明 | ① | 如果您想要创建新的模块/包并添加到一个`FILEPATH`中,Python 也会查看新目录下的定制模块/包。您可以使用 sys 模块添加新的`FILEPATH`。 | ### 练习 2-88:检查内置模块和 sys.builtin_module | ① | `>>> dir(__builtins__)``['ArithmeticError', 'AssertionError', 'AttributeError',``[... omitted for brevity]``'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']``>>> import sys``>>> for name in sys.builtin_module_names:``...     print (name, end=' ')``...``_abc _ast _bisect _blake2 _codecs _codecs_cn _codecs_hk``[... omitted for brevity]``faulthandler gc itertools marshal math mmap msvcrt nt parser sys time winreg xxsubtype zlib` | #### 说明 | ① | 在 Python 解释的会话中,可以使用`dir()`函数查看内置模块。您还可以使用`for`循环方法来查看内置模块中的内容。还有其他方法可以查看这些信息,这只是检查内置和模块的一种方法。 | ### 练习 2-89:在尝试和例外练习中使用简单的导入系统模块 | ① | `# ex2_89.py``import sys``file = 'C://Users//brendan//Documents//test.txt'``try:``with open (file) as test_file:``for line in test_file:``print(line.strip())``except:``print('Could not open {}'. format(file))``sys.exit(1)` | | ② | `C:\Users\brendan> python C://Users//brendan//Documents/ex2_89.py``Could not open C://Users//brendan//Documents//test.txt` `# output` | | ③ | `>>> with open('C://Users//brendan//Documents//test.txt', 'w') as f:``...     f.write('This is a test file only.')``...     f.write('Study Python, Network, Linux and Automation all from a single book.')``...``26``67` | | ④ | `>>> with open('C://Users//brendan//Documents//test.txt', 'r') as f:``...     print(f.read())``...``This is a test file only.` `# output``Study Python, Network, Linux, and Automation all from a single book.` `# output` | | ⑤号 | `C:\Users\brendan> python C://Users//brendan//Documents/ex2_89.py``This is a test file only.` `# output``Study Python, Network, Linux, and Automation all from a single book.` `# output` | #### 说明 | ① | 首先,创建一个名为`ex2_89.py`的脚本,并复制代码。这个示例脚本被创建并保存在`C://Users//brendan//Documents`文件夹中。 | | ② | 从 Windows 命令行提示符或 Windows PowerShell,运行`ex2_89.py`脚本。或者,如果您已经为 Python 设置了 Notepad++,运行代码。由于指定文件夹中没有`text.txt`文件,Python 会触发自定义异常错误。该脚本有一个`import sys`模块语句,它在输出异常错误后触发了一个退出操作。 | | ③ | 现在创建一个`test.txt`文件并添加几行。 | | ④ | 要检查内容,请打开并阅读文件,然后打印这些行。 | | ⑤号 | 现在运行 Python 脚本(`ex2_89.py`)来打印内容。这是一个小练习,但是我希望你能从这个练习中学到一些东西。如果你没有,那么让我们回去复习一下我们在这里学到的东西。 | ### 练习 2-90:通过制作计算器来理解 Lambdas | ① | `>>> def sum(x, y):``...     return x +y``...``>>> sum(3, 2)``5` | | ② | `>>> sum = lambda x, y: x + y``>>> sum(3, 2)``5` | | ③ | `>>> lamb_cal = [lambda x,y:x+y, lambda x,y:x-y, lambda x,y:x*y, lambda x,y:x/y]``>>> lamb_cal[0]`` at 0x000001BF34AFC3A0>``>>> lamb_cal0``5``>>> lamb_cal[1]`` at 0x000001BF34AFC670>``>>> lamb_cal1``1``>>> lamb_cal[2]`` at 0x000001BF34AFC700>``>>> lamb_cal2``6``>>> lamb_cal[3]`` at 0x000001BF34AFC790>``>>> lamb_cal3``1.5` | #### 说明 | ① | 这是一个简单的函数,将`x`和`y`相加并返回总和。 | | ② | 你可以在一行代码中使用 lambda 来达到同样的效果。这是对①中函数的重新创建,但使用了更少的行。 | | ③ | 您已经使用 lambda 在一行中创建了一个简单的计算器。计算器是不言自明的;`lamb_cal`可以执行基本算术。随着我们对 Python 编码越来越熟悉,在某些情况下我们可以应用 lambdas。您不必使用 lambda,但是您可以在本练习中看到 lambdas 的实际价值。 | ### 回顾:模块概念 这里是一个回顾: * 模块是 Python 代码,可以使用一组`.py`文件格式的变量、函数和类。 * 你可以通过发出`import module_name`来导入一个模块。 * 模块的默认位置是在 Python 安装期间设置的。 * Python 内置库是一组可重用的 Python 程序,包含各种代码。 * `dir()`内置函数允许您查看软件包中的模块。 * 如果您没有所需的模块,您可以创建一个,添加`FILEPATH`,然后添加您的包。 ![img/492721_1_En_2_Figk_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_2_Figk_HTML.gif)如果你想复习本章所学内容或者第二、三次重复练习,下载`ex2_1_to_2_90.txt`用于练习;这个文件包含了从练习 2-1 到练习 2-90 的所有练习。如果你对 Python 完全陌生,我建议你至少重复前面的练习三次。 URL: [`https://github.com/pynetauto/apress_pynetauto/tree/master`](https://github.com/pynetauto/apress_pynetauto/tree/master) ## 摘要 我希望你在这一章中一直在键盘上忙碌,并尝试了一些基本的 Python 练习。像 Python 这样的解释型编程语言是为了模仿人类语言而设计的。很多年前,我在大学学习语言的语用学。根据《剑桥词典》的解释,语用学是“对语言如何受到使用环境影响的研究,对语言如何被用来获得事物或执行动作的研究,以及对词语如何表达与其表面意思不同的事物的研究。”随着我们对 Python 作为一种编程语言或工具越来越熟悉,我们必须继续学习与计算机(机器)交流,向计算机发出一组指令,并解释 Python 的反馈或错误。我们已经通过实际例子和练习介绍了最基本的 Python 概念。学习编程语言的最好方法是通过更多与真实场景相关的练习。现在我们已经学习了基础知识,让我们进行更多的练习,将概念与第三章中的真实场景联系起来。下一章包含一般的 Python 练习以及与工作相关的练习。 # 三、更多 Python 练习 罗马人借用了一个古老的雅典概念,并把它变成了一句谚语:“Repetito master sturdiorum”,意思是“重复是一切学习之母。”在前一章中,您练习了基本的 Python 概念,但是在本章中,您将获得更多关于更相关的主题和场景的练习。练习的某些部分将包含与你在本书后半部分将要写的脚本直接相关的内容。当我们做本章中的每个练习时,让你的手指打字,但是让你的大脑思考某个练习如何帮助你自动完成部分工作。 ![img/492721_1_En_3_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_3_Figa_HTML.jpg) ## 为练习做准备 本书假设您是使用 Python 的网络自动化新手。当我们看一个典型的 IT 工程师的日常任务时,系统工程师和网络工程师不需要像 DevOps 工程师那样多打字。这只是工作的性质,因为 DevOps 工程师总是在移动,编写代码和创建应用作为各种项目的一部分。换句话说,对于大多数阅读本书的非 DevOps IT 人员来说,您的时间将花在安装、配置和解决您所在领域的各种问题上。在工作时间找时间练习将是一项挑战,因为学习编码和开发自动化应用是没有报酬的。然而,几乎所有的企业 IT 团队都在试图推动他们的 IT 工程师开始学习编程语言并自动化重复性的任务。作为一名优秀的员工和模范 IT 工程师,您可能一直在利用工作时间之外的时间学习 Python。在第一次尝试时,并不是所有人都能理解可编程性概念或学习 Python 基础知识;只要坚持不懈,不断尝试,直到你做到。克服挑战的唯一方法是通过更多的练习。如果您有具体的 Python 挑战,请养成在 Google 上搜索该主题并进一步阅读相关内容的习惯。现在,让我们使用相关的例子获得更多的 Python 实践。 在第二章,你学习了 Python 的基础知识。本章将帮助你进行更多的练习,为使用 Python 进行网络自动化做好准备。你练习得越多,你就会越好。 ### 练习 3-1:将一个列表和一个元组连接成一个列表 | ① | `>>> fruits = ['apple', 'orange', 'mango']``>>> vegetables = ('broccoli', 'potato', 'spinach')``>>> favorites = fruits + list(vegetables)``>>> print(favorites)``['apple', 'orange', 'mango', 'broccoli', 'potato', 'spinach']` | #### 说明 | ① | 在列表中创建一个变量,在元组中创建一个变量。使用`list()`方法将元组转换成列表。将一个列表和一个元组连接并合并成一个列表。 | ### 练习 3-2:使用 Python 作为计算器 | ① | `>>> eigrp, ospf, rip = 90, 110, 120``>>> path1, path2, path3 = 3, 6, 9``>>> admin_distance = (eigrp * path1) + (ospf * path2) + (rip * path3)``>>> print(admin_distance)``2010` | #### 说明 | ① | 使用 Python 作为计算器,计算管理距离。 | ### 练习 3-3:做一些基本的字符串格式()练习 | ① | `>>>` `name = 'Hugh'``>>>` `age = 15``>>>` `detail = 'His name is %s and he is %d.'``>>>` `print(detail %(name, age))``His name is Hugh and he is 15.` | | ②#1#2#3#4 | `>>>` `Name, age, height = 'Hugh', 15, 174.5``>>>` `detail = ('His name is {}, he is {} and {} cm tall.'.format(name, age, height))``>>>` `print(detail)``His name is Hugh, he is 15 and 174.5 cm tall.``>>>` `detail = ('His name is {0}, he is {1} and {2} cm tall.'.format(name, age, height))``>>>` `print(detail)``His name is Hugh, he is 15 and 174.5 cm tall.``>>>` `detail = ('His name is {name}, he is {age} and {height} cm tall.'.format(name='Joshua', age=16, height=178))``>>>` `print(detail)``His name is Joshua, he is 16 and 178 cm tall.``>>>` `detail = ('His name is {0}, he is {age} and {height} cm tall.'.format('Michael', age=12, height=170))``>>>` `print(detail)``His name is Michael, he is 12 and 170 cm tall.` | | ③ | `>>>` `person = {'height':174.5, 'name':'Hugh', 'age':15}``>>>` `print('{name} is {age} years old and {height} cm tall.'.format(**person))``Hugh is 15 years old and 174.5 cm tall.` | #### 说明 | ① | 这是一个基本的字符串(`%s`)和数字(`%d`)格式示例。 | | ② | #1.`format()`示例:默认参数#2.`format()`示例:位置参数#3.`format()`示例:关键字参数#4.`format()`示例:混合参数 | | ③ | 这是一个`str.format(**mapping)`的例子。这是 Python 中使用的参数解析的一个例子,所以要特别注意例子中使用的两个星号。 | ### 练习 3-4:询问用户名 用 Notepad++写代码,如图 3-1 所示。 ![img/492721_1_En_3_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_3_Fig1_HTML.jpg) 图 3-1。 用户输入,感谢用户 | ① | `# ex3_4.py``print('Please enter your name: ')``name = input()``print('Thank you, ', name)` | #### 说明 | ① | 创建一些询问用户名的 Python 代码。您将使用它作为用户名和密码工具开发的基础。当你询问某人的名字时,你会得到一些乐趣。 | ### 练习 3-5:获取用户名:版本 1 编写代码并保存为`ex3_5.py`,在 Notepad++中运行代码。(见图 3-2 ) ![img/492721_1_En_3_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_3_Fig2_HTML.jpg) 图 3-2。 用户输入,说“嗨” | ① | `#ex3_5.py``name = input('Please enter your name: ')``print(f'Hi, {name}.')` | #### 说明 | ① | 重申一下`ex3_4.py`,在`ex3_5.py`写一些简化代码。代码行的数量已经减少。在本练习中,您将学习如何将两行代码缩减为一行代码,并使用缩写格式方法。 | ### 练习 3-6:获取用户名:版本 2 在 Notepad++上,输入以下代码,将文件另存为`ex3_6.py`并运行代码。(见图 3-3 )。 ![img/492721_1_En_3_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_3_Fig3_HTML.jpg) 图 3-3。 用户输入,获取格式正确的输入 | ① | `#Ex3_6.py``import re``name = input("Enter your name: ")``while True:``while not re.match("^[a-zA-Z]+$", name):``name = input("Enter your name: ")``else:``print(name)``exit()` | #### 说明 | ① | 使用`ex3_5.py`作为基础,您刚刚创建了一个简单的 Python 应用,它根据一些要求询问某人的姓名。您已经导入了`re`(正则表达式)内置 Python 模块,以强制用户提供一个以字母开头的名称,该名称只接受字母。`^[a-zA-Z]+$`表示以一个或多个字母开头并以一个字母结尾的任何字符。因此,yomu 不能输入数字或特殊字符作为名称。如果用户不能遵守这条规则,脚本会一直询问,直到用户提供预期的响应。一旦收到正确的响应,就会打印出名字并退出应用。你将在第九章学到更多关于正则表达式的知识。整整一章将致力于学习正则表达式以及如何使用`re`模块。脚本完全是关于数据处理以及我们如何处理它们。我们必须掌握正则表达式,克服对正则表达式的恐惧,才能成为自信的编程工程师。 | ### 练习 3-7:获取用户名:版本 3 输入代码并在 Notepad++上将文件保存为`ex3_7.py`。运行如图 3-4 所示的代码。 ![img/492721_1_En_3_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_3_Fig4_HTML.jpg) 图 3-4。 用户输入,将用户名脚本放入函数中 | ① | `# ex3_7.py``import re``def get_name():`1 `name = input("Enter your name: ")`1 `while True:`2 `while not re.match("^[a-zA-Z]+$", name):`3 `name = input("Enter your name: ")`2 `else:`3 `print(name)`3 `exit()``get_name()` | #### 说明 | ① | 创建`ex3_7.py`脚本并运行它,学习如何基于`ex3_6.py`创建函数并将其转换成函数。最后一条语句`get_name()`将触发脚本运行。这是与`ex3_6.py`相同的脚本,但是我们已经将它转换成一个函数,这样你就可以理解迭代过程。 | ### 练习 3-8:添加临时文件路径,将 ex3_7.py 作为模块导入,然后运行脚本 | ① | `C:\Users\brendan>` `python``Python 3.9.1 (tags/v3.9.1:1e5d33e, Dec  7 2020, 17:08:21) [MSC v.1927 64 bit (AMD64)] on win32``Type "help", "copyright", "credits" or "license" for more information.``>>>``import``Enter your name:` `Brendan``Brendan``C:\Users\brendan>` | | ② | `# If your script is under a different folder, for example, under 'C:\Users\brendan\Documents\'. You have to manually add the Filepath using append.``>>>` `import sys``>>>``sys.path.append('C:\\Users\\brendan\\Documents\\')``>>>` `print(sys.path)``['', 'C:\\Python39\\python39.zip', 'C:\\Python39\\DLLs', 'C:\\Python39\\lib', 'C:\\Python39', 'C:\\Python39\\lib\\site-packages',``'C:\\Users\\brendan\\Documents\\'``>>>` `import ex3_7.py``9``Enter your name: Ryan``Ryan` | #### 说明 | ① | 如果在 Python 安装过程中选择了将 Python 3.9 添加到路径,则应该能够将 Python 脚本作为模块调用,并在 Windows 操作系统上的任何位置运行它。如果您忘记了,您可以选择添加一个临时文件路径,如示例②所示。 | | ② | 打开 Python 命令提示符并添加临时文件路径,以便 Python 可以读取在练习 3-7 中创建的 Python 模块。这里,您添加了一个新的(临时)文件路径,并导入了一个自定义模块,以便从您的解释器会话中运行,从而了解导入和自定义模块是如何工作的。 | ### 练习 3-9:使用逗号在字符串之间添加空格 | ① | `>>` `print('Around' + 'the' + 'World' + 'in' + '100' + 'days.')``AoundtheWorldin100days.` `# by default, no spaces are added when '+' is used``>>>``print('Around ' + 'the ' + 'World ' + 'in ' + '100 ' + 'days.')``Around the World in 100 days` `.``>>>` `print('Around', 'the', 'World', 'in', '100', 'days.')``Around the World in 100 days.` `# adds spaces automatically` | #### 说明 | ① | 练习前面的练习并比较输出。请注意,在上一个练习中使用了逗号,并且输出在字符串之间添加了空格。 | ### 练习 3-10:练习 if;if 和 else 和 if,elif,和 else | ① | `>>>` `y = 5``>>>` `if x > y:``...     print('x is greater than y.')``...``>>>` `# No output as if the statement was False``>>>` `x = 3``>>>` `y = 5``>>>` `if x < y:``...` `print('x is less than y.')``...``x is less than y.` `# prints output as if the statement was True``>>>` `y = 5``>>>` `if x >= y:``...     print('x is greater or equal to y.')``...` `else:``...` `print('x is smaller than y.')``...``x is smaller than y.` `#  prints output of else statement``>>>` `x = 3``>>>` `y = 5``>>>` `z = 3``>>>` `if x < y > z:``...` `print('y is greater than x and greater than z.')``...``y is greater than x and greater than z.` `# all conditions met``>>>` `x = 5``>>>` `y = 10``>>>` `if x < y:``...` `print('x is smaller than y.')``...` `elif x > y:``...` `print('x is greater than y.')``...``else``...` `print('x is equal to y.')``...``x is smaller than y.` `# if the statement was satisfied` | #### 说明 | ① | 你刚刚练了`if`;`if`和`else`;以及`if`、`elif`和`else`。这些练习是不言自明的;这是 Python 的一大魅力。 | ### 练习 3-11:练习 end= ' ' | ① | `>>>` `for n in range (1, 11):``...` `print(n)``...``1``2``3``[omitted for brevity]``9``10``>>>` `for n in range (1, 11):``...``print(n,``...``12345678910``>>>``>>>` `for n in range (1, 11):``...``print(n, end=' ')``...``1 2 3 4 5 6 7 8 9 10``>>>``>>>` `for n in range (1, 11):``...``print(n, end=',')``...``1,2,3,4,5,6,7,8,9,10,``>>>``>>>` `for n in range (1, 11):``...``print(n, end='/')``...``1/2/3/4/5/6/7/8/9/10/``>>>``>>>` `for n in range (1, 11):``...``print(n, end='|')``...``1|2|3|4|5|6|7|8|9|10|``>>>` | #### 说明 | ① | 用`end=`练习每个`for ~ in range`语句,并比较结果的变化。 | ### 练习 3-12:练习音域内的~音 | ① | `>>>` `for n in range (2, 11):``...` `print(("Creating VLAN") + str(n))``...``Creating VLAN2 Creating VLAN3 Creating VLAN4 Creating VLAN5 Creating VLAN6 Creating VLAN7 Creating VLAN8 Creating VLAN9 Creating VLAN10` | #### 说明 | ① | 当您必须循环通过几条线路来向网络设备添加配置时,`for ~ in range()`变得非常方便。我们将在后面的 Python 网络自动化实验中应用前面的代码。试着熟悉这个例子。 | ### 练习 3-13:为线 in ~练习 | ① | `>>>` `txt1 = "Seeya later Alligator"``>>>` `for line in txt1:``...` `print(line)``...``S``e``e``[omitted for brevity]``o``r``>>>` `txt1 = "Seeya later Alligator"``>>>` `for line in txt1:``...` `print(line, end='')``...``Seeya later Alligator` | #### 说明 | ① | 对于字符串,可以使用`for line in ~`方法,对于数字会得到和`for n in range()`方法一样的效果。 | ### 练习 3-14:使用 split()方法 | ① | `>>>` `txt1 = "Seeya later Alligator"``>>>``print(txt1.split())``['Seeya', 'later', 'Alligator']``>>>``print(txt1.split( ))``['Seeya', 'later', 'Alligator']``>>>` `print(txt1.split('e', 1))``['S', 'eya later Alligator']` `#splits at first instance of 'e``>>>` `txt2 = (txt1.split('e', 1))``>>>``print(txt2[0])``S``>>>` `txt3 = (txt1.split('a', 3))``>>>``print(txt3)``['Seey', ' l', 'ter Allig', 'tor']``>>>``print(txt3[1])``L` | #### 说明 | ① | 当与正则表达式一起使用时,对字符串索引和拼接进行足够的练习会成为一个强大的工具。不幸的是,要成为一名优秀的程序员,我们必须熟悉一般的数据处理:数据收集、数据处理和数据流控制。 | ### 练习 3-15:练习 lstrip()、rstrip()、strip()、upper()、lower()、title()和大写() | ① | `>>>` `australia = ' terra australis incognita '``>>``print(australia.title())``Terra Australis Incognita``>>>``print(australia.rstrip().upper())``TERRA AUSTRALIS INCOGNITA``>>>``x = (australia.lstrip().lower())``>>>``print(x.capitalize())``Terra australis incognita``>>>``print(australia.strip().upper())``TERRA AUSTRALIS INCOGNITA` | #### 说明 | ① | 您刚刚完成了大写和小写以及脱衣法练习。你能想出一些有趣和相关的单词让你练习吗?通过改变单词使其对你有更多的意义,给你的练习增添一些乐趣。 | ### 练习 3-16:创建一个文件并以四种不同的方式读取它 | ① | `>>>` `with open('test123.txt', 'w') as f:``...` `f.write('This is line 1.\nThis is line 2.\nThis is line 3.\n')``...``48` | | ② | `>>>` `with open('test123.txt', 'r') as f:``...` `lines = f.readlines()``...` `print(lines)``...``['This is line 1.\n', 'This is line 2.\n', 'This is line 3.\n']` | | ③ | `>>>` `with open('test123.txt', 'r') as f:``...` `lines = list(f)``...` `print(lines)``...``['This is line 1.\n', 'This is line 2.\n', 'This is line 3.\n']` | | ④ | `>>>` `f = open('test123.txt', 'r')``>>>` `lines = f.readlines()``>>>` `print(lines)``['This is line 1.\n', 'This is line 2.\n', 'This is line 3.\n']``>>` `f.close()` | | ⑤号 | `>>>` `f = open('test123.txt', 'r')``>>>` `lines = list(f)``>>>` `print(lines)``['This is line 1.\n', 'This is line 2.\n', 'This is line 3.\n']``f.close()` | #### 说明 | ① | 在`w`模式下创建`test123.txt`,写点东西。 | | ② | 这是用一个`readlines()`例子的`open`方法。 | | ③ | 这是用一个`list()`例子的`open`方法。 | | ④ | 这是一个简单的`open()`方法和一个`readlines()`例子。使用后请务必关闭文件。 | | ⑤号 | 这是一个简单的`open()`方法和一个`list()`例子。确保发出`f.close()`关闭文件。 | ### 练习 3-17:阅读和输出文件以获得更详细的理解 | ① | `>>>` `with open('test123.txt', 'w') as f:``...` `f.write(' this is a lower casing line.\n')``...``33``>>>` `with open('test123.txt', 'r') as f:``...` `print(f.read())``...``this is a lower casing line.` | | ② | `>>>` `with open('test123.txt', 'a') as f:``...` `f.write('THIS IS AN UPPER CASING LINE. \nThisIsACamelCasingLine.\n')``...``58` | | ③ | `>>>` `with open('test123.txt', 'r') as f:``...` `print(f.read())``...``this is a lower casing line. THIS IS AN UPPER CASING LINE.``ThisIsACamelCasingLine` `.``>>>` `with open('test123.txt', 'r') as f:``...` `print(f.readline())``...``this is a lower casing line.``>>>` `with open('test123.txt', 'r') as f:``...` `print(f.readlines())``...``[' this is a lower casing line. \n', 'THIS IS AN UPPER CASING LINE .\n', 'ThisIsACamelCasingLine.\n']` | | ④ | `>> >>>` `with open('test123.txt', 'r') as f:``...` `print(f.read().lower().strip())``...``this is a lower casing line. this is an upper casing line. thisisacamelcasingline.` | | ⑤号 | `>>>` `with open('test123.txt', 'r') as f:``...` `x = (f.read().lower().strip())``...` `y = set(x.split())``...` `print(y)``...` `print(len(y))``...``{'lower', 'casing', 'a', 'this', 'is', 'upper', 'thisisacamelcasingline.', 'an', 'line.'} 9` | #### 说明 | ① | 创建一个新的`test123.txt`文件。由于文件以`w`模式打开,它将覆盖在练习 3-17 中创建的文件。按照本书中所示的相同方式写入文件。 | | ② | 您可以在一种模式下向文件中写入更多内容。 | | ③ | 尝试在三种不同的模式下读取和打印:`read()`、`readline()`、`readlines()`。你看出区别了吗? | | ④ | 练习使用其他方法来操作字符串和空格。 | | ⑤号 | 您将同时使用`set()`和`split()`方法来计算文件中的单词和字符总数。 | ### 练习 3-18:使用 getpass()模块和用户输入 对于本练习,打开 Python IDLE(解释器)编写代码。 | ① | `>>>` `import getpass``>>>` `def get_pwd():``...` `username = input('Enter username : ')``...` `password = getpass.getpass()``...` `print(username, password)``...``>>>` `get_pwd()``Enter username :` `admin``Password: **********``admin mypassword` | #### 说明 | ① | 使用`import`加载`getpass`库。使用`def`把你的密码改成一个函数。然后用`input()`功能询问用户名;然后使用`getpass.getpass()`模块询问用户的密码。输入密码时,`getpass()`模块会隐藏输入的密码。为了测试,我们打印出了用户名和密码。当创建 SSH 和 Telnet 到网络设备的登录请求时,`getpass()`模块很方便。 | ### 练习 3-19:理解编码和解码的区别 | ① | `>>>` `text_1 = 'Network Automation'``>>>` `print(text_1)``Network Automation``>>>` `byte_1 = text_1.encode()``>>>` `print(byte_1)``b'Network Automation '` | | ② | `>>>` `byte_2 = b'Mission completed.'``>>>` `print(byte_2)``b'Mission completed. '``>>>` `text_2 = byte_2.decode()``>>>` `print(text_2)``Mission completed.` | #### 说明 | ① | 计算机以比特(0 和 1)进行通信,计算机以字节处理文件。但我们人类希望以明文形式与机器交流,因此需要编码和解码。这是一个将字符串转换成字节的简单练习。您将在第十三章的 Telnet Python 实验室练习中看到这一点。特别记下这个练习。 | | ② | 在 Python 3 中,所有字符串都被识别为 Unicode,必须使用`decode()`方法来转换字节,字节是计算机理解的二进制流数据。在路由器和交换机等机器上执行命令时,要注意字节到字符串的转换和字符串到字节的转换,以避免代码执行错误。 | ### 练习 3-20:用 csv 模块处理 Python 中的 CSV 文件 ![img/492721_1_En_3_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_3_Fig6_HTML.jpg) 图 3-6。 在 Excel 中检查 2020_router_purchase.csv ![img/492721_1_En_3_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_3_Fig5_HTML.jpg) 图 3-5。 运行 CSV 脚本在 Windows PowerShell 上创建 Excel 文件 | ① | `# Use the following information to create .py called ex3_20.py and save it to your Documents folder.``# ex3_20.py``import csv``with open ('C://Users//brendan//Documents//2020_router_purchase.csv', 'w', newline='' ) as csvfile:``filewriter = csv.writer (csvfile, delimiter = ',', quotechar = '|', quoting = csv.QUOTE_MINIMAL)``filewriter.writerow (['Site', 'Router_Type', 'IOS_Image', 'No_of_routers', 'Unit_price($)', 'Purchase_Date'])``filewriter.writerow(['NYNY', 'ISR4351/K9', 'isr4300-universalk9.16.09.05.SPA.bin', 4, '$ 9100.00', '1-Mar-20'])    filewriter.writerow(['LACA', 'ISR4331/K9', 'isr4300-universalk9.16.09.05.SPA.bin', 2, '$ 5162.00', '1-Mar- 20'])``filewriter.writerow(['LDUK', 'ISR4321/K9', 'isr4300-universalk9.16.09.05.SPA.bin', 1, '$ 2370.00', '3-Apr- 20'])``filewriter.writerow(['HKCN', 'ISR4331/K9', 'isr4300-universalk9.16.09.05.SPA.bin', 2, '$ 5162.00', '17-Apr-20'])``filewriter.writerow(['TKJP', 'ISR4351/K9', 'isr4300-universalk9.16.09.05.SPA.bin', 1, '$ 9100.00', '15-May-20'])``filewriter.writerow(['MHGM', 'ISR4331/K9', 'isr4300-universalk9.16.09.05.SPA.bin', 2, '$ 5162.00', '30-Jun-20'])` | | ② | 打开 Windows 命令提示符或 Windows PowerShell,运行 Python 代码创建您的 CSV 文件-->(见图 3-5 )。`PS C:\Users\brendan>``cd Documents``PS C:\Users\brendan\Documents>``dir ex3_20.py``#``PS C:\Users\brendan\Documents>``python ex3_20.py``#``PS C:\Users\brendan\Documents>``dir 2020_router_*``#` | | ③ | 使用 Microsoft Excel 打开文件并检查新创建的文件。它看起来应该类似于图 3-6 。 | | ④ | `#` `For Windows users``>>>` `f = open('C://Users//brendan//Documents//2020_router_purchase.csv', 'r')``>>>` `f``<_io.TextIOWrapper name='C://Users//brendan//Documents//2020_router_purchase.csv' mode="r" encoding=``'cp1252'``#` `For Linux users (optional)``>>>` `f = open ('2020_router_purchase.csv', 'r')``>>>` `f``<_io.TextIOWrapper name = ''2020_router_purchase.csv' mode = 'r' encoding = 'UTF-8'>` | | ⑤号 | `>>>` `f = open('C://Users//brendan//Documents//2020_router_purchase.csv', 'r')``>>>` `routers = f.read()``>>>` `routers``'Site,Router_Type,IOS_Image,No_of_routers,Unit_price($),Purchase_Date\nNYNY,ISR4351/K9,isr4300- universalk9.16.09.05.SPA.bin,4,$ 9100.00,1-Mar-20\nLACA,ISR4331/K9,isr4300- universalk9.16.09.05.SPA.bin,2,$ 5162.00,1-Mar-20\nLDUK,ISR4321/K9,isr4300- universalk9.16.09.05.SPA.bin,1,$ 2370.00,3-Apr-20\nHKCN,ISR4331/K9,isr4300- universalk9.16.09.05.SPA.bin,2,$ 5162.00,17-Apr-20\nTKJP,ISR4351/K9,isr4300-``universalk9.16.09.05.SPA.bin,1,$ 9100.00,15-May-20\nMHGM,ISR4331/K9,isr4300-``universalk9.16.09.05.SPA.bin,2,$ 5162.00,30-Jun-20\n'``>>>` `print(routers)``Site,Router_Type,IOS_Image,No_of_routers,Unit_price($),Purchase_Date``NYNY,ISR4351/K9,isr4300-universalk9.16.09.05.SPA.bin,4,$ 9100.00,1-Mar-20``LACA,ISR4331/K9,isr4300-universalk9.16.09.05.SPA.bin,2,$ 5162.00,1-Mar- 20``LDUK,ISR4321/K9,isr4300-universalk9.16.09.05.SPA.bin,1,$ 2370.00,3-Apr- 20``HKCN,ISR4331/K9,isr4300-universalk9.16.09.05.SPA.bin,2,$ 5162.00,17-Apr-20``TKJP,ISR4351/K9,isr4300-universalk9.16.09.05.SPA.bin,1,$ 9100.00,15-May-20``MHGM,ISR4331/K9,isr4300-universalk9.16.09.05.SPA.bin,2,$ 5162.00,30-Jun-20``>>>` `f.close ()` | | ⑥ | `>>> with open('C://Users//brendan//Documents//2020_router_purchase.csv', 'r') as f:``...     routers = f.readlines()``...     for router in routers:``...         print(router, end=(" "))``...``Site,Router_Type,IOS_Image,No_of_routers,Unit_price($),Purchase_Date``NYNY,ISR4351/K9,isr4300-universalk9.16.09.05.SPA.bin,4,$ 9100.00,1-Mar-20``LACA,ISR4331/K9,isr4300-universalk9.16.09.05.SPA.bin,2,$ 5162.00,1-Mar- 20``LDUK,ISR4321/K9,isr4300-universalk9.16.09.05.SPA.bin,1,$ 2370.00,3-Apr- 20``HKCN,ISR4331/K9,isr4300-universalk9.16.09.05.SPA.bin,2,$ 5162.00,17-Apr-20``TKJP,ISR4351/K9,isr4300-universalk9.16.09.05.SPA.bin,1,$ 9100.00,15-May-20``MHGM,ISR4331/K9,isr4300-universalk9.16.09.05.SPA.bin,2,$ 5162.00,30-Jun-20` | #### 说明 | ① | 使用这些信息创建一个名为`ex3_20.py`的新代码文件。该文件需要保存在您的`Documents`文件夹中。 | | ② | 然后打开您选择的命令提示符,运行 Python 命令,使用`ex3_20.py`脚本创建一个 CSV 文件。 | | ③ | 在 Microsoft Excel 中打开新创建的`2020_router_purchase.csv`文件进行检查。 | | ④ | 如果您打开并阅读该文件,您可以看到 Windows 创建的文件将使用`cp1252`的编码,而在 Linux 上,UTF-8 编码将被用作默认的解码方法。 | | ⑤号 | 使用`read()`读取文件。内容是一长串数据。如果您使用`print()`,那么新的行将显示为单独的行,并且变得更容易阅读。 | | ⑥ | 如果您不想被结尾的`f.close()`语句所困扰,您也可以使用`with open`命令来打开并读取文件。在这个例子中,我们使用`f.readlines()`方法分别读取每一行,然后使用`for`循环调用 CSV 文件中的每一行。花点时间仔细研究一下`read()`、`readlines()`和`readline()`方法之间的区别。 | ### 练习 3-21:输出 CSV 文件 | ① | `>>>` `f = open('C://Users//brendan//Documents//2020_router_purchase.csv', 'r')``>>>` `for line in f:``...` `print(line.strip())``...``Site,Router_Type,IOS_Image,No_of_routers,Unit_price($),Purchase_Date NYNY,ISR4351/K9,isr4300-universalk9.16.09.05.SPA.bin,4,$ 9100.00,1-Mar-20 LACA,ISR4331/K9,isr4300-universalk9.16.09.05.SPA.bin,2,$ 5162.00,1-Mar-20 LDUK,ISR4321/K9,isr4300-universalk9.16.09.05.SPA.bin,1,$ 2370.00,3-Apr-20 HKCN,ISR4331/K9,isr4300-universalk9.16.09.05.SPA.bin,2,$ 5162.00,17 -Apr-20``TKJP,ISR4351/K9,isr4300-universalk9.16.09.05.SPA.bin,1,$ 9100.00,15 -May-20``MHGM,ISR4331/K9,isr4300-universalk9.16.09.05.SPA.bin,2,$ 5162.00,30 -Jun-20``>>>` `f.close()` | #### 说明 | ① | 像在普通文本文件中一样,您可以使用`for`循环方法读取每一行并一次打印一行。确保使用`strip()`方法删除任何前导或尾随空格、换行符或制表符。 | ### 练习 3-22:从 CSV 文件中查找 Cisco ISR 4331 路由器的价格 | ①*#1**#2**#3**#4**#5* | `>>>` `f = open('C://Users//brendan//Documents//2020_router_purchase.csv', 'r')``>>>` `x = f.read().split('\n')``>>>` `x``['Site,Router_Type,IOS_Image,No_of_routers,Unit_price($),Purchase_Date', 'NYNY,ISR4351/K9,isr4300- universalk9.16.09.05.SPA.bin,4,$ 9100.00,1-Mar-20', 'LACA,ISR4331/K9,isr4300- universalk9.16.09.05.SPA.bin,2,$ 5162.00,1-Mar-20', 'LDUK,ISR4321/K9,isr4300- universalk9.16.09.05.SPA.bin,1,$ 2370.00,3-Apr-20', 'HKCN,ISR4331/K9,isr4300- universalk9.16.09.05.SPA.bin,2,$ 5162.00,17 -Apr-20', 'TKJP,ISR4351/K9,isr4300- universalk9.16.09.05.SPA.bin,1,$ 9100.00,15 -May-20', 'MHGM,ISR4331/K9,isr4300- universalk9.16.09.05.SPA.bin,2,$ 5162.00,30 -Jun-20', '']``>>>` `y = x[2]``>>>` `y``'LACA,ISR4331/K9,isr4300-universalk9.16.09.05.SPA.bin,2,$ 5162.00,1-Mar-20'``>>>` `z = y.split(',')``>>>` `z``['LACA', 'ISR4331/K9', 'isr4300-universalk9.16.09.05.SPA.bin', '2', '$ 5162.00', '1-Mar-20']``>>>` `price_4331 = z[4]``>>>` `print(price_4331)``$ 5162.00` | #### 说明 | ① | 在本例中,您刚刚练习了从 CSV 文件中读取数据并提取特定信息。#1.`read().split()`方法读取文件,分割项目,并将项目保存在每行的列表中。#2. *x* 的索引 2 被分配给变量`y`。#3.再次,`split()`将字符串分解成一个列表。该列表被分配给一个变量`z`。#4.`z`的第五元素或索引 4 元素被分配给一个名为`price_4331`的变量。#5.一台思科 4331 的价格印在屏幕上。 | ### 练习 3-23:计算购买路由器的总成本:无模块 | ① | `# ex3_23.py``total = 0.0``with open ('C://Users//brendan//Documents//2020_router_purchase.csv', 'r') as f:``headers = next (f)``for line in f:``line = line.strip ()``devices = line.split (',')``devices[4] = devices[4] .strip ('$')``devices[4] = float (devices[4])``devices[3] = int (devices[3])``total += devices[3] * devices[4]``print('Total cost: $', total)` | | ② | `C:\Users\brendan\Documents>` `python ex3_23.py``Total cost: $ 78842.0` | #### 说明 | ① | 使用在练习 3-23 中学到的方法,创建一个名为`ex3_23.py`的程序来计算路由器的总开销。我们把总数的初始值定为 0.0 美元。使用列表中的索引 3 和索引 4 来查找购买路由器的费用。为了避免计算过程中可能出现的错误,我们使用`header=next(f)`来跳过第一行的标题信息。在各种数据类型之间进行拆分、剥离和转换。将脚本保存在`Document`文件夹中,并确保您更改了文件路径以适应您的计算机设置。 | | ② | 运行 Python 应用,找出购买路由器的总成本。根据我们的剧本,总费用是 78,842 美元。这些练习看起来很深奥而且没有意义,但是在处理有意义的数据和开发实际工作应用时会变得有用。 | ### 练习 3-24:计算购买路由器的总成本:使用 csv 模块 | ① | `# ex3_24.py``import csv``total = 0.0``with open ('C://Users//brendan//Documents//2020_router_purchase.csv') as f:``rows = csv.reader (f)``headers = next (rows)``for row in rows:``row [4] = row [4].strip ('$')``row [4] = float(row [4])``row [3] = int(row [3])``total += row[3] * row[4]``print('Total cost: $', total)` | | ② | `C:\Users\brendan\Documents>` `python ex3_24.py``Total cost: $ 78842.0` | #### 说明 | ① | 我们使用 Python 内置的`csv`模块重新创建了同一个应用。首先,导入`csv`模块,并遵循相同的条带化和数据类型转换过程。编程中有一句名言,“剥猫皮的方法不止一种。” | | ② | 运行新代码,但总成本是一样的。 | 正如我们之前讨论的,本章只涵盖了基本的和特定的 Python 主题。我建议您阅读其他基本的 Python 书籍,并深入本书中讨论的主题。干得好!您已经完成了基本的 Python 练习,为一些 Python 网络自动化做好了准备,但更多高级主题将在后面的章节中介绍。 ### 练习 3-25:转换 dd-mmm-yy 并计算天数和年数的差值 在从事网络工作时,您会注意到不同的供应商在他们的软件中有不同的日期和时间格式。一个很好的例子是思科路由器和交换机的日期格式。当我处理从实际的 Cisco IOS 软件中收集的数据时,我不得不花一个小时来转换和处理特定格式的 Cisco IOS 发布日期,并计算出 IOS 映像自发布日期以来的时间。这个练习来自一个真实的生产示例脚本。下面是给出的 IOS 发布日期的格式:12-Jul-17。 目标是操纵日期并将其转换为正确的格式,这样 Python 就可以计算出 IOS 自发布之日起有多长时间了。请注意,您的日期将与本例中给出的结果不同,因为它使用`today()`作为计算日期。首先遵循示例①,然后遵循迭代示例②。 | ①`#1``#2``#3``#4``#5``#6``#7``#8``#9``#10``#11``#12``#13` | `# Follow this exercise in Python IDLE``>>>` `from datetime import datetime``>>>` `IOS_rel_date = '12-Jul-17'``>>>` `x = IOS_rel_date.replace('-', ' ')``>>>` `y = datetime.strptime(x, '%d %b %y')``>>>` `y``datetime.datetime(2017, 7, 12, 0, 0)``>>>``y.date()``>>>` `y_day = y.date()``>>>` `datetime.today()``datetime.datetime(2020, 4, 15, 11, 56, 3, 370515)``>>>` `datetime.today().date()``datetime.date(2020, 4, 15)``>>>` `t_day = datetime.today().date()``>>>` `delta = t_day - y_day``>>>` `print(delta.days)``1008``>>> years = round(((delta.days)/365), 2)``2.76` | | ② | `# Reiterated and reduced version of example 1:``>>>` `from datetime import datetime``>>>` `x = IOS_rel_date.replace('-', ' ')``>>>` `y_day = (datetime.strptime(x, '%d %b %y')).date()``>>>` `t_day = datetime.today().date()``>>>` `delta = t_day - y_day``>>>` `years = round(((delta.days)/365), 2)``>>>` `print(years)``2.76` | #### 说明 | ① | #1.从日期时间库中导入日期时间模块。我们需要这个库来完成我们的挑战。#2.以字符串形式输入 IOS 发布日期,开始本练习。#3.用空格替换`-`。#4.使用 datetime 模块的`strptime`方法将`x`转换为正确的日期格式。`%d`代表天数,`%b`代表缩写月份,如一月、六月、七月,`%y`代表缩写年份,没有前两位数字。Python 的 datetime 模块期望日期采用特定的格式,我们正在尝试规范化格式以实现我们的目标。#5.现在输入`y`来检查当前的格式。它有两个由逗号分隔的 0,即小时和分钟。我们必须去掉这两个零。#6.现在使用`y`上的`date()`方法删除小时和分钟信息。在应用之前先检查一下。这是解决一半挑战所需的日期格式。我们现在达到了 50%。#7.现在我们对日期格式满意了,所以把它赋给变量`y_day`。#8.是时候处理今天的日期了。为了找到 IOS 发布以来的天数,我们需要计算出今天是几号,然后减去发布日期。再次使用`datetime()`模块,这次是在`today()`上。#9.如果结果可以接受,那么在后面追加`.date()`以删除时间元素。这将以正确的格式返回日期。#10.现在将 9 号中的对象赋给一个变量`t_day`。#11.两个日期之间的差值为`t_day`–`y_day`。#12.打印结果,它将返回天数的增量。#13.现在将 delta 除以 365 天,将天数转换为年数。使用`round()`功能给出最后两位小数。这个单独的练习将让您对如何处理特定的数据有所了解,在本例中是日期。 | | ② | 该实例是实例 1 的重复版本。我将一些函数压缩成一行,以缩短转换过程。这只是数据操作的一个例子,还有许多其他方法可以完成同样的转换。尽管如此,重要的是理解思维过程,这样你就可以根据你的情况来调整它。最后,如果您有 10 分钟的时间,请访问以下网站,查看有哪些 *strftime* 代码格式: [`https://strftime.org/`](https://strftime.org/) 。 | ## 探索 Python IDE 环境 完成本章后,您需要开始考虑 Python 代码开发的集成开发环境(IDE)。在文本编辑器中工作很简单,但也有其局限性。在 IDE 中工作并不是强制性的,但是当您编写更复杂和更长的 Python 代码时,当使用文本编辑器(如 Windows 上的 Notepad、EditPlus 或 Notepad++或 Linux 上的 vi、Emacs、Gedit 或 nano)时,您的编码效率会下降。ide 提供了许多标准文本编辑器所没有的特性。当您与团队成员协作时,文档的版本控制突然变得至关重要。许多组织使用带有云存储的 GitHub 作为他们的代码版本控制解决方案。不过,在这个阶段,您不必担心 GitHub 或版本控制。你必须先学会爬行,然后才能走路。因此,在本书的这一部分之外,不再讨论这个话题。在本书中,您不必开始使用 IDE 应用,但是我鼓励您一次探索一个,并找到与您的编码风格最匹配的应用。在企业 Linux 系统上,你很少看到图形用户界面(GUI),所以这里只讨论兼容 Windows 的 ide。为了帮助您探索适用于 Windows 的各种 Python IDEs,请参见表 3-1 。虽然 Jupyter Notebook (Anaconda)不是一个 IDE,但它包含在表 3-1 中,给你提供了学习 Python 的另一条途径。 表 3-1。 Python IDEs 和 Anaconda (Jupyter 笔记本) | 推荐的 IDE | 利弊,下载网址 | | --- | --- | | **PyCharm*** | **优点**:功能丰富,直观的用户界面,支持跨平台,专为 Python 打造,免费社区版,对初学者和专家都很友好**缺点**:付费专业版**URL** : [`https://www.jetbrains.com/pycharm/download/`](https://www.jetbrains.com/pycharm/download/) | | **微软 Visual Studio** | 优点:Windows 友好,易于使用(尤其是如果你是 Windows 用户),功能丰富,社区版本免费**缺点**:界面启动会比较慢。**URL** : [`https://visualstudio.microsoft.com/vs/features/python/`](https://visualstudio.microsoft.com/vs/features/python/) | | **月食** | 优点:多种编程语言支持,熟悉的用户界面,免费缺点 : JRE (JDK)依赖,对初学者来说很难设置,老的 IDE**URL** : [`https://www.eclipse.org/downloads/packages/installer`](https://www.eclipse.org/downloads/packages/installer) | | **原子** | **优点**:由 GitHub 的电子文本编辑器开发,支持跨平台和多种编程语言,通过扩展和插件提供丰富的功能,关于应用编程接口(API)的良好文档,免费,易于使用,轻便但仍然强大缺点:不如 PyCharm 直观,比 PyCharm 更接近文本编辑器**URL** : [`https://atom.io/`](https://atom.io/) | | **崇高正文 3** | **优点**:流行的代码编辑器,具有快速、稳定、成熟的用户界面;许多具有模块化方法和可扩展性的特性缺点:不像其他 IDE 那样通用,感觉更像是文本编辑器而不是 IDE,必须购买许可证才能继续使用,没有调试选项**URL** : [`https://www.sublimetext.com/3`](https://www.sublimetext.com/3) | | **anaconda–jupyter 笔记型电脑** | 优点:初学者友好,优秀的 Python 学习工具,基于网络,被科学界用于数据分析**缺点**:不是 IDE,基于网络,系统资源多**URL** : [`https://jupyter.org/install`](https://jupyter.org/install) | ## 摘要 这一章很简短,但是包含了比 Python 的`print()`函数和语法更多的内容。这一章作为一个测试章节,让你对下一章的内容有所了解;通过查看一些新的 Python 模块、处理文件以及使用简单的 Python 代码处理数据,您学习了更多的 Python 基础知识。第 2 和第三章应该作为本书后面介绍的 Python 网络自动化实验室的良好基础。如果你仍然没有完全理解练习的某些部分,可以回头重复练习两到三次。如果你有时间,在你读到第十一章的时候,你应该把这两章的练习重复三遍。第 4 至 6 章将着重于构建虚拟集成的 Python 网络自动化实验室,从安装 VMware Workstation Pro 开始。是时候进入第四章学习 VMware 工作站基础知识和一般虚拟化技术基础知识了。VMware Workstation 及其虚拟化技术将帮助您构建完整的虚拟网络实验室,并作为实验室中虚拟机的粘合剂,因此我建议您完整阅读下一章。 ## 故事时间 2:机器大战人类,第一次对抗 ![img/492721_1_En_3_Figb_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_3_Figb_HTML.jpg) 根据维基百科,第一次机器与人类的对抗发生在 19 世纪 70 年代左右的美国,当时一位名叫约翰·亨利的矿工为了修建铁路隧道向自动蒸汽钻机发起挑战。约翰·亨利的故事在美国童谣中流传到了年轻一代。在过去,要钻一条铁路隧道,铁路矿工必须使用锤子、凿子和钻头来打洞,并使用炸丨药来炸毁陆地以建造隧道。这场人机竞赛在美国变得如此著名,以至于约翰·亨利对抗蒸汽机的故事成了美国民间传说。根据这个故事,隧道挖掘比赛进行了一天半,以钻炸丨药洞和铺设轨道。 根据这个民间故事,约翰·亨利打败了蒸汽钻,并在捍卫自己的工作时取得了胜利。可悲的是,根据这个故事,约翰·亨利由于疲劳和比赛后的严重压力而死于心脏病发作。当然,第二天和接下来的几天,蒸汽钻继续钻孔。显而易见的是,约翰·亨利和他的同事们的工作正在自动化,美国铁路公司毫不犹豫地用机器取代了矿工。第一次工业革命期间,农业和制造业也发生了同样的事情,人类任务的自动化今天仍在继续,未来也将继续。 根据维基百科,“自动化是一种在最少人工协助下执行流程或程序的技术。”我们都同意自动化总体上提高了人类的生活质量,但自动化硬币总是有两面。约翰·亨利是一个简单的矿工,以挖洞为生,几乎没有接受过机械或机器操作方面的教育。对他来说,学习如何操作重型机械,蒸汽钻,这是 19 世纪 70 年代最新最伟大的技术,是一项挑战。因此,对于这个可怜的家伙来说,从一份旧工作转换到一份新工作几乎是不可能的。看看今天的自动化和 IT 自动化,特别是,现有的工程师已经发现自己处于与 John Henry 相同的情况,并且随着 IT 技术的快速发展,将来会有更多的工程师面临困难。信息技术的进步将取代许多现有的信息技术工作。尽管如此,好消息是,新技术也将为那些不断学习如何在尖端 IT 行业生存的人创造新的工作和机会。如果你的工作实现了自动化,你发现自己失业了,你会因为失业而感到痛苦,但如果你一直在准备过渡到 it 自动化,你可能也会乐在其中。 *来源:* [`https://en.wikipedia.org/wiki/John_Henry_`](https://en.wikipedia.org/wiki/John_Henry_) `(folklore)` # 四、VMware Workstation 简介 VMware Workstation Pro 是企业 IT 人员最常用的终端用户桌面虚拟化解决方案之一。它允许 It 工程师从他们自己的 PC/笔记本电脑上构建和测试类似真实世界的虚拟机和网络设备。学习在一台计算机上构建一个功能完整的集成虚拟实验室,并在一台 PC 上掌握 Python 网络自动化基础知识是本书的根本目标。本章旨在向您介绍最流行、最通用的 VMware 产品 VMware Workstation 15 Pro 的基础知识,以及基本的虚拟化概念。阅读本章后,您将获得以下知识:您将了解类型 1 和类型 2 虚拟机管理程序之间的区别,了解提供不同桌面虚拟化解决方案的各种 IT 供应商,了解如何安装 VMware Workstation,了解如何在 VMware Workstation 上执行常规管理,以及了解 VMware Workstation 的网络适配器如何工作。 ![img/492721_1_En_4_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figa_HTML.jpg) ## VMware 工作站概览 VMware 于 1998 年在加利福尼亚州的帕洛阿尔托成立,二十多年来,该公司一直在发展壮大并引领着企业虚拟化市场。除了本书的前三章,本书将依赖于 VMware 的桌面虚拟化技术,即 VMware Workstation 15 Pro。在开始学习 Python 自动化之前,您必须对各种 IT 技术保持开放的态度。学习编写 Python 代码并不保证您的网络自动化尝试会成功。您将需要学习正则表达式和 Linux 管理,这些主题通常超出了网络工程师的舒适范围,但是对于编写功能性代码是必不可少的。没错;通往 Python 网络自动化的道路崎岖不平。对于一个初学 Python 的人来说,独自学习 Python 语法可能需要几个月的时间,而且可能需要几年才能称自己是这种编程语言的专家。然而,更大的挑战是将 Pythonic 概念和语法应用到您的工作中。您需要在当前网络和一般 IT 知识的基础上学习和构建 Python 知识。编写 Python 代码只是你旅程的第一步。它要求您更好地理解 Linux、网络、简单的数学、正则表达式、与几个 API 的交互等等。 在时间和资源有限的情况下,您正试图学习使用 Python 在网络自动化领域取得良好开端所需的基本技能,所有学习都将在一台 Windows PC(或一台笔记本电脑)上进行。为此,我们必须利用 VMware 桌面虚拟化解决方案的强大功能。使用 VMware Workstation 15 的原因很简单。它为像您这样的 Python 编程新手提供了一个实用而简单的虚拟实验室,可以在多种场景中学习。 首先,我们必须熟悉基本的虚拟化概念和 VMware Workstation 15 的一些基本功能。即使您是各种虚拟化解决方案(包括 VMware 产品)的长期用户,您也会从本章中学到一些新东西,所以我希望您能完整地阅读本章。读完这一章后,你将毫无问题地理解本书的其余内容,并最终建立一个 Python 网络自动化实验室。在前一章中,您在您的主机 Windows PC 上安装了 Python 3,并在 Windows 10 上学习了 Python 基础知识。但是,在本章和后面的章节中,将指导您使用 VMware Workstation 15 Pro 在同一台 Windows 主机上安装两种版本的 Linux 分发服务器。因此,您也可以轻松地学习 Linux 服务器的 Python 基础,并在第 7 和 8 章开始学习 Linux 基本管理技能。要运行各种操作系统,我们得使用虚拟化技术来帮助我们在同一个主机平台上运行多台机器;VMware Workstation 15 可以帮助我们。作为本章准备工作的一部分,您将在您的 PC 上下载并安装 VMware Workstation 15。之后,您将学习 VMware Workstation 15 的基础知识,然后在以下两章中安装最新版本的 Ubuntu 20 和 CentOS 8 服务器。在整本书中,这些服务器将用于教您 Linux 管理、Python 安装和 Python 模块安装,以及如何将 CentOS 服务器构建为一体化 IP 服务服务器,该服务器可在本书末尾的概念验证(PoC)实验室中用作小刀工具。 您对 VMware Workstation 15 的特性和基本虚拟化功能了解得越多,您就越能更有效地构建这个测试实验室环境。在为企业 IT 解决方案设计和构建 PoC 实验室时,通常只部署昂贵且功能极其强大的设备来测试最简单的概念。有时需要真实的设备,例如,在与硬件相关的系统性能测试中进行压力测试。此外,购买合适设备的采购过程可能需要数周甚至数月时间。如何找到具有足够电源电压和冷却能力的机架空间,以保持您的实验室平稳运行?此外,物理实验室包括使用正确连接器类型的正确电缆,以适应供应商的设备接口,这可能会降低您构建公司的概念验证实验室的速度。通常情况下,公司实验室只有公司的几个人使用,因此浪费了数千美元。这完全是浪费公司的钱,而且,球场的这一边肯定没有绿色 IT。由于过去十年虚拟化技术的进步,越来越多的设备正在迁移到虚拟化平台,正如您所看到的基于云的计算和软件即服务 IT 产品的爆炸式增长,任何 IT 即服务都以这样或那样的形式利用虚拟化技术的力量。 VMware Workstation 15 Pro 是 VMware 最受欢迎的跨平台桌面虚拟化计划,支持 Windows 和 Linux。对于 macOS 操作系统,VMware Fusion 提供了相同的功能。有一个免费版的 VMware Workstation Player,但是它缺少让您的实验室成为一个完整实验室所需的所有功能。因此,您将使用 VMware Workstation 15 Pro。您将在 Microsoft Windows 10 主机系统上安装一系列软件应用,每次安装一个。根据维基百科的数据,2017 年的数据表明,使用 Windows 操作系统帐户的台式机/笔记本电脑用户的比例约为 88%,macOS 用户约占 10%,最后,世界上只有 2%的 Linux 台式机/笔记本电脑用户。从我自己的经验来看,即使在 IT 行业内部,也反映了类似的用户百分比。然而,由于基于 Python 的企业级自动化解决方案是首选的编程语言,Linux 是默认的操作系统,具有更高的灵活性、更高的性能、更低的拥有成本和更好的安全性。 您可以从 VMware 官方网站下载 Workstation 15 Pro Evaluation for Windows,并享受 30 天的免费试用期。30 天后,您可以购买 15.5 版许可证并永久激活评估软件。写这本书的时候,最新的 VMware Workstation for Windows 版本是 15.5.1,build 15018445,但是你下载的版本会更新一些(15.5 版甚至 16.x 版),但是应该还是能和本书推荐的大部分软件一起工作。供您参考,VMware Workstation 的版本 13 从未发布,版本 14 已发布,但它在 GNS3 集成方面有多个错误。奇怪的是,GNS3 和思科 VIRL IOS 软件在 VMware Workstation 12.5.x 或 15.x 上运行最佳,但在 14.x 版本上则不行。因此,如果您正在使用 VMware 14,您必须升级到最新的 VMware Workstation 15.x 版本,以避免出现任何问题。还建议您禁用自动更新功能,以保持您的软件版本始终稳定。这个兼容性建议是基于我的软件兼容性测试。图 4-1 显示了本书中使用的 VMware Workstation 15 Pro 的产品信息(文件名:`VMware-workstation-full-15.5.1-15018445.exe`)。 ![img/492721_1_En_4_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig1_HTML.jpg) 图 4-1。 关于 VMware Workstation 15 Pro 即使您还不熟悉虚拟化的概念和该软件,学习本章内容也不会有任何问题,因为本书假设您以前没有使用该 VMware 产品的经验。那些不熟悉像这样的桌面虚拟化程序的人仍然可以通读这一章来熟悉这个软件和一些基本的桌面虚拟化概念。对于那些有多年虚拟化经验的人,请浏览本章并跳到下两章,其中包括安装 Ubuntu 20.04 和 CentOS 8 Linux 虚拟机(VM)。 ### 第 1 类与第 2 类虚拟机管理程序 关于虚拟化(虚拟机管理程序)软件,首先要了解的是类型 1 和类型 2 虚拟机管理程序之间的区别。为了解释类型 1 和类型 2 虚拟化软件之间的差异,如果主机硬件不需要单独的操作系统软件,则虚拟化软件直接在主机硬件上执行。它被归类为 1 类虚拟机管理程序。如果托管硬件需要单独的操作系统软件,则被归类为第 2 类虚拟机管理程序。更直白地说,类型 1 虚拟机管理程序通常用于业务用途,类型 2 虚拟机管理程序设计用于个人和实验用途。这并不意味着 Type-2 不应该用于商业用途。但是,在商业环境中,几乎没有理由使用类型 2 虚拟机管理程序而不是类型 1 虚拟机管理程序,因为类型 1 虚拟机管理程序提供了更好的稳定性、高可用性和更好的可扩展性功能。Type-1 虚拟机管理程序系列,也称为*裸机*,是指发布时没有内置操作系统软件的企业级服务器平台。典型的第 1 类虚拟机管理程序产品包括 VMware 的 ESXi、微软的 Hyper-V for Servers、Citrix 的 XenServer 和 Oracle VM。 相比之下,为桌面应用开发的第二类虚拟机管理程序的示例包括 VMware 的 VMware Workstation、Fusion、Player、Oracle 的 VM VirtualBox(以前为 Sun Microsystems)、Microsoft 的 VirtualPC 和 Red Hat 的企业虚拟化。第二类虚拟机管理程序也称为桌面(或托管)虚拟机管理程序。本书中使用的 VMware Workstation 15 Pro 运行在 Windows 10 主机上,使其成为第 2 类虚拟机管理程序。 ### 用于概念验证实验室的 VMware Workstation Pro VMware Workstation 15 Pro 是 VMware 的旗舰桌面虚拟化计划。版本 1 发布于 1999 年,最新版本是在撰写本书时的 16 版本。虽然这本书是基于 15.x 版本,但是你基于 16.x 版本搭建实验室不会有问题,安装和使用这个桌面程序是直观的任务,不需要用户培训就可以使用。它支持 Windows 和 Linux 操作系统。运行在操作系统之上的虚拟程序,如 VMware Workstation,有时被称为*实验虚拟化程序*,最新的软件试用版可从 VMware 的官方下载页面获得。基本安装很简单,只需按几个按钮就可以完成安装。如果您没有有效的许可证,该程序将在 30 天的试用许可证上运行。经过 30 天的试用期后,您可以在购买许可证后继续使用该软件。 当然,在选择实验室使用的桌面虚拟化软件时,您会发现来自微软和甲骨文的其他流行软件。甲骨文的 VirtualBox 和微软的 Hyper-V 被认为是 VMware Workstation 的替代品,但因为软件兼容性和系统稳定性,我们将使用 VMware Workstation 15 Pro。考虑了企业级虚拟化计划,如 VMware 的 ESXi 和微软的 Hyper-V,但此类第 1 类虚拟机管理程序对硬件规格的要求更高。例如,类型 1 虚拟机管理程序软件需要安装在特定于虚拟机管理程序的硬件(裸机)服务器上。此外,您还需要一台单独的客户端 PC 来远程控制服务器,以运行第 1 类虚拟机管理程序软件。此外,第 1 类虚拟机管理程序通常部署在数据中心或公司通信室,并不意味着是移动的。 ## 使用 VMware Workstation 之前 安装 ESXi 6.5 等第 1 类虚拟机管理程序软件时,安装过程中有一些严格的硬件要求。该软件具有检查最低硬件要求的内置过程。幸运的是,对于在您的主机操作系统上安装 VMware Workstation 15 Pro,没有或只有最低限度的硬件先决条件。如果您的计算机的 CPU 和主板支持虚拟化技术,那么您可以开始安装。新型电脑或笔记本电脑的 BIOS 出厂时启用了虚拟化技术选项。如果您使用的是较旧类型的 PC 或笔记本电脑,则需要启用此设置,但这是在 PC 上安装软件之前唯一需要更改的内容。直到最近,VMware 的虚拟化解决方案倾向于支持英特尔的 CPU 架构,而不是 AMD 的 CPU 架构。以我自己的经验来看,英特尔 CPU 在性能、兼容性和效率方面远远优于 AMD CPUs。当然,AMD 最近在 CPU 开发上投入了大量资金,但 AMD CPUs 更适合喜欢 PC 游戏的个人用户。 在安装 VMware Workstation 15 Pro 之前,请确保在进入笔记本电脑/PC 主板的 BIOS 后将虚拟化支持设置为启用,如图 4-2 所示。 ![img/492721_1_En_4_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig2_HTML.jpg) 图 4-2。 检查英特尔 CPU 主板虚拟化支持设置 如果在 BIOS 中将英特尔 VT(或 AMD-V)的使用设置为禁用,则将设置更改为启用,然后按 F10 键保存更改并正常登录 Windows 10。表 4-1 和 4-2 列出了主要主板和笔记本电脑制造商的 BIOS 条目密钥。进入 BIOS 的功能键或组合键因制造商而异,这里提供了最常见的制造商键供您参考。如果您在表中找不到您的笔记本电脑制造商,请访问制造商的网站并找到 BIOS 信息以更改 BIOS 设置。 表 4-2。 主板的 BIOS 设置入口键 | 制造商 | BIOS 键/组合键 | | --- | --- | | 亚斯洛克 | F11 | | 华硕 | F12/F2/F8/F9 | | 生物星(BIOSTAR) | F7/F9 | | 康柏电脑公司(美著名电脑制造商) | F10 | | EMTec 公司 | F9 | | 富士康 | F7/Esc 键 | | 十亿字节 | F12 | | 大功率(High Power)ˌ高压(High Pressure)ˌ高性能(High Performance)ˌ高聚物(High Polymer) | F9 | | 美国英特尔公司(财富 500 强公司之一ˌ以生产 CPU 芯片著称) | F10 | | 水平规ˌ水准仪(Level Gauge) | F12 | | 中规模集成电路(medium-scale integration 的缩写) | F11 | | 佩帕托伦 | F11 | | 三星电子 | 经济社会委员会 | | 其他的 | 请参考相应的供应商网站 | 表 4-1。 笔记本电脑制造商提供的 BIOS 设置入口密钥 | 制造商 | BIOS 组合键 | | --- | --- | | 宏基电脑公司 | F2/Del(新建)F1/Ctrl+Alt+Esc(旧) | | 华硕 | F2/F10/Del/Insert/Alt+F10 | | 康柏电脑公司(美著名电脑制造商) | F1/F2/F10/Del | | 幽谷 | F1/Del/F12/F3(新)(当戴尔徽标出现在屏幕上时按下)Ctrl+Alt+Enter/Fn+Esc 或 Fn+F1/Ctrl+F11(旧) | | 电子机器 | F2/Del 键 | | 日本富士通公司 | 第二子代 | | 门 | F1/F2 | | 大功率(High Power)ˌ高压(High Pressure)ˌ高性能(High Performance)ˌ高聚物(High Polymer) | F1/F2/F6/F9/F10/F11/Esc 平板电脑:F10/F12 | | 联想(电脑的品牌名) | F1/F2/F11(新)Ctrl+Alt+F3/Ctrl+Alt+Ins/Fn+F1 (舊) | | 水平规ˌ水准仪(Level Gauge) | F10/F11/F12 | | 三星电子 | F2/F4 | | 索尼 | f1/F2/F3/辅助 | | 东芝 | F2/F1/ESC/F12 | | 其他的 | 请参考相应的供应商网站 | 这里还有一个关于 VMware Workstation 版本的提醒:VMware Workstation 15 Pro 在发布时的最新版本是 15.5.1 build-15018445。另请注意,版本 14 显示了与本书中使用的一些应用的兼容性问题。该问题被确定为 GNS3 Docker 程序下载流程的安全文件和 VIRL 集成要求。请使用与 GNS3 兼容的最新 15.5 版本。本书使用 15.5.1 版,即编写本章时的最新版本 15.5。如果您没有版本 15 的副本,请使用版本 12。版本 12 还与 GNS3、VIRL 软件和 GNS3 Docker 映像兼容且稳定。在本书的第一稿和技术审查之后,VMware 发布了第 16 版。您应该能够使用 16 版或最新版本,并仍然遵循这本书的内容。目前,请使用 VMware Workstation 15.x Pro 或 16.x 或 12.x,但不要使用 14.x。 ![img/492721_1_En_4_Figb_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figb_HTML.gif)阅读本章之前,请在您的 Windows PC 上下载并安装 VMware Workstation 15(或 16) Pro。下载和安装很简单,但是如果您需要帮助,本书提供了下载安装指南的链接。 从以下 URL 下载 VMware Workstation 15/16: URL: [`https://www.vmware.com/au/products/workstation-pro/workstation-pro-evaluation.html`](https://www.vmware.com/au/products/workstation-pro/workstation-pro-evaluation.html) “第四章准备工作-安装 VMware Workstation”指南可从以下网址获得: URL: [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) ### VMware Workstation 15 Pro 的下一款产品是什么? 作为本章的准备工作,您必须在主机上安装 VMware Workstation 15 Pro。在安装两台 Linux 虚拟服务器和导入 GNS3 VM 之前,我们将为那些不熟悉虚拟化技术和该计划的人快速回顾一下菜单选项。如果您确信自己对 VMware Workstation 功能了如指掌,请跳到第五章。无论如何,你可以把虚拟机(VM)想象成另一台运行在主机操作系统(Type-2)上的独立计算机。用户可以通过 Workstation 15 Pro 的主用户控制台与虚拟机进行交互,并使用各种操作系统安装媒体文件来创建和安装虚拟机,然后配置、导入、导出、控制和管理虚拟机的所有方面。您已经看到了 VMware Workstation 15 Pro 的主用户控制台,并且已经注意到用户界面简洁直观。然而,虚拟机的操作和行为比您最初想象的要复杂得多。虚拟机看起来像一个应用,但它是一台具有真实硬件计算机的许多固有特性的计算机。了解 Workstation 15 Pro 的最佳方式是使用它,但为了让您的学习更容易一些,我们将在下一部分介绍一些与控制台相关的术语。让我们看看用户界面、菜单和虚拟网络编辑器。 快速浏览后,您将被引导至下载和安装几个虚拟机的链接,并使用 GNS3 `.ova`文件下载和导入一个虚拟机。您必须在本章结束时创建所有三个虚拟机,因为本书的其余部分将依赖这些虚拟机来学习 Linux、学习正则表达式、安装 Python 和网络相关模块,以及学习如何在虚拟实验室中编写 Python 网络自动化脚本。为了模拟真实的实验场景,您将测试各种网络概念,并熟悉最常用的文件共享服务。例如,本书的最后几章将包含基于 CentOS 8 虚拟机的 FTP、SFTP 和 TFTP 服务器的实验场景。 或者,如果您想在这里创建新的虚拟机,然后将它们导出到真实的生产环境中,您可以使用 VMware Converter 软件来实现。虚拟机可以在 VMware Workstation 上预留,然后在以后进行转换,将虚拟机转换为在 VMware 的 vSphere 6.5 上运行的类型 1 虚拟机。现在,我们先来看看 Workstation 15 Pro 的主用户控制台及其菜单。 ### VMware Workstation 15 Pro 用户控制台 Workstation 15 Pro 用户窗口(或控制台)允许您安装、控制和管理各种类型的虚拟机。图 4-3 显示了 VMware Workstation 15 Pro 的主用户界面和默认按钮。该图将为您提供有关菜单和按键的各种功能的信息。使用该程序不需要特殊技能,安装后您可以轻松地创建和使用虚拟机。现在,让我们学习菜单和主要功能。 ![img/492721_1_En_4_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig3_HTML.jpg) 图 4-3。 VMware 工作站,用户窗口 ## VMware Workstation 15 Pro 的基本操作 图 4-4 描绘了 VMware Workstation 15 Pro 的主用户界面,这是您管理虚拟机的主要界面。与 Windows 10 和 Oracle VirtualBox 上的 Microsoft Hyper-V 相比,VMware Workstation Pro 拥有最友好的用户界面和用户触手可及的强大功能。让我们快速学习一下这个程序最基本的功能。 ![img/492721_1_En_4_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig4_HTML.jpg) 图 4-4。 VMware 工作站,主用户窗口 ### VMware Workstation Pro:基本操作 | ![img/492721_1_En_4_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig5_HTML.jpg)图 4-5a。启动 VMware Workstation Pro | **启动 VMware Workstation 15 Pro(图**[](#Fig5)****)**:** **您可以使用 Windows 桌面上的 VMware 快捷方式或 Windows 10 的“开始”菜单启动该程序:开始➤程序➤ VMware ➤ VMware 工作站。** | | ![img/492721_1_En_4_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig6_HTML.jpg)图 4-5b。电源选项 1 | **打开/关闭电源,暂停或重置虚拟机(图****4-5b****)**:打开/关闭或暂停虚拟机的一种方法是从虚拟机菜单中选择虚拟机:虚拟机➤电源➤启动来宾。 | | ![img/492721_1_En_4_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig7_HTML.jpg)图 4-5c。电源选项 2 | 打开/关闭、暂停或重置虚拟机的第二种方法是在虚拟机库中选择虚拟机(图 4-5c )。右键单击虚拟机,然后选择电源➤启动来宾。 | | ![img/492721_1_En_4_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig8_HTML.jpg)图 4-5d。电源选项 3 | 打开/关闭、暂停或重置虚拟机的第三种方式是使用用户窗口顶部的电源键选项(图 4-5d )。 | | ![img/492721_1_En_4_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig9_HTML.jpg)图 4-5e。要迁移到虚拟机 | 要移动到虚拟机(从主机),请按 Ctrl+G。要使用正在运行的虚拟机,请使用鼠标左键单击虚拟机控制台或按键盘上的 Ctrl+G(图 4-5e )。 | | ![img/492721_1_En_4_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig10_HTML.jpg)图 4-5f。要退出虚拟机 | 要退出虚拟机(将光标移回主机),请按 Ctrl+Alt。要在使用虚拟机时将鼠标光标退出 Windows 主机,请按 Ctrl+Alt 键退出主机(图 4-5f )。 | 更详细的使用说明,请参考 [`https://docs.vmware.com/en/VMware-Workstation-Pro/15.0/workstation-pro-15-user-guide.pdf`](https://docs.vmware.com/en/VMware-Workstation-Pro/15.0/workstation-pro-15-user-guide.pdf) 。 ### VMware 工作站菜单 表 4-3 一目了然地显示了所有菜单。最常用的功能标有星号(*)。您可能会发现检查功能并跳到创建虚拟机很有帮助。 表 4-3。 菜单一览 | 菜单屏幕截图 | 菜单功能 | | --- | --- | | ![img/492721_1_En_4_Figc_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figc_HTML.jpg)文件菜单 | **文件菜单:**创建新的虚拟机打开一个新窗口打开虚拟机*扫描虚拟机关闭选项卡连接到外部服务器、vCenter 和 vSphere 服务器连接到 vCloud Air 创建 p2v(物理到虚拟)导出到 OVF 文件*连接到虚拟磁盘退出* | | ![img/492721_1_En_4_Figd_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figd_HTML.jpg)编辑菜单 | 编辑菜单:切口复制粘贴虚拟网络编辑器*偏好* | | ![img/492721_1_En_4_Fige_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fige_HTML.jpg)查看菜单 | **查看菜单:**全屏查看*统一模式控制台视图使屏幕适合来宾操作系统使屏幕适合窗口自动调整大小自定义视图 | | ![img/492721_1_En_4_Figf_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figf_HTML.jpg)VM 菜单 | **VM(虚拟机):**电源*可移动设备中止发送 Ctrl+Alt+Del 键*抓住打字快照管理*屏幕捕获经营 VMware 工具安装设置* | | ![img/492721_1_En_4_Figg_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figg_HTML.jpg)标签菜单 | **标签页菜单:**主页前往主屏幕下一个选项卡上一个选项卡 | | ![img/492721_1_En_4_Figh_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figh_HTML.jpg)帮助菜单 | **帮助:**帮助在线文档支持暗示输入许可证密钥*VMware 工作站注册软件更新关于 VMware Workstation | | ![img/492721_1_En_4_Figi_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figi_HTML.jpg)虚拟机电源图标 | **虚拟机电源图标:**客人在*客人离开*来宾暂停继续来宾*通电关机中止重置开机时进入 BIOS | | ![img/492721_1_En_4_Figj_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figj_HTML.jpg)发送 Ctrl+Alt+Del 图标 | 向虚拟机发送 Ctrl+Alt+Delete | | ![img/492721_1_En_4_Figk_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figk_HTML.jpg)快照图标 | 拍摄此虚拟机的快照将此虚拟机恢复到其父快照管理此虚拟机的快照 | | ![img/492721_1_En_4_Figl_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figl_HTML.jpg)屏幕模式选项图标 | 显示或隐藏库显示或隐藏缩略图栏进入全屏模式进入统一模式 | | ![img/492721_1_En_4_Figm_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figm_HTML.jpg)显示或隐藏控制台视图图标 | 显示或隐藏控制台视图 | | ![img/492721_1_En_4_Fign_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fign_HTML.jpg)自由拉伸图标 | 自由伸展 | ## 虚拟网络适配器 初学者问的关于 VMware Workstation 15 Pro 的最常见问题之一是如何使用不同的虚拟网络适配器设置。很难一言以蔽之地提供答案;您可以阅读 VMware 网站上不必要的冗长文档,但我们大多数人没有时间阅读供应商提供的文档。在这里,我们将介绍虚拟机网络入门的基础知识。第一次使用的用户需要知道每个虚拟网络适配器之间有什么不同,以及如何使用每个适配器。解释这一点的最佳方式是查看用户可以使用哪些虚拟网络适配器。尝试理解每种网络适配器类型的定义,然后查看程序中不同的适配器设置,以揭示每个适配器在您的 PC 上是如何配置的。首先,让我们看一下虚拟网络编辑器,查看现成的虚拟网络适配器设置。 ### 虚拟网络编辑器概述 VMware Workstation 在虚拟交换机中提供虚拟网络功能,其中三个虚拟交换机映射到三个特定网络,用户可以根据需要创建多达 17 个虚拟交换机。不同的网络类型将在下一节详细讨论。因此,您可以拥有多达 20 个虚拟交换机(VMnet0 到 VMnet19),每个虚拟交换机代表一个平面网络或子网。就像在真实的生产环境中一样,您可以将多个虚拟机连接到同一个网络(虚拟交换机)。首先,打开虚拟网络编辑器菜单,查看哪些默认连接类型可用。 **#** **任务** 1 启动 VMware Workstation Pro 时,最佳做法是通过右键单击桌面图标并选择“以管理员身份运行”选项来启动程序,如图 4-6 所示。这样,您就可以使用完全管理员级别的权限来启动程序,以更改任何程序设置。 ![img/492721_1_En_4_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig11_HTML.jpg) 图 4-6。 VMware Workstation Pro,以管理员身份运行 1b 如果您不想每次启动程序时都单击“以管理员身份运行”选项,您可以更改快捷方式属性,使程序以管理员模式运行。转到 VMware Workstation Pro 的桌面图标,单击鼠标右键,选择属性,如图 4-7 所示。 ![img/492721_1_En_4_Fig12_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig12_HTML.jpg) 图 4-7。 VMware Workstation Pro,快捷图标属性 在快捷方式属性菜单中,单击兼容性,在设置选项下,选中“以管理员身份运行此程序”,然后单击应用和确定按钮。现在你的程序将总是以管理员身份运行(见图 4-8 )。 ![img/492721_1_En_4_Fig13_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig13_HTML.jpg) 图 4-8。 VMware Workstation Pro,更改属性兼容性设置 2 现在,从菜单中打开并查看虚拟网络适配器,选择编辑➤虚拟网络编辑器(图 4-9 )。 ![img/492721_1_En_4_Fig14_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig14_HTML.jpg) 图 4-9。 选择虚拟网络编辑器 3 当虚拟网络编辑器第一次打开时,正如预期的那样,有三个虚拟交换机(VMnets)。与真实的网络适配器一样,虚拟适配器也可以称为虚拟 NIC 或虚拟网络接口。虚拟网络编辑器是管理不同虚拟网络的默认控制塔,如添加/删除网络、启用/禁用 DHCP 等(图 4-10 )。 ![img/492721_1_En_4_Fig15_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig15_HTML.jpg) 图 4-10。 虚拟网络编辑器,默认适配器类型 ![img/492721_1_En_4_Figo_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figo_HTML.gif)在图 4-10 所示的虚拟网络编辑器窗口中,点击帮助按钮,这将打开 VMware 官方网站上的文档“使用虚拟网络编辑器” ### 虚拟网络接口描述 这里是我们一直在讨论的不同网络模式和网络术语的快速概述。如果您曾经学习过或研究过思科 CCNA,您应该已经熟悉网络行业中的这些网络术语。如果你正在学习 CCNP 或者完成了 CCNP 的学业,你可能会将这些网络适配器模式与真实的生产环境联系起来。然而,即使您不熟悉网络技术,理解以下解释也不会有任何困难。如前所述,有三个现成的网络适配器,但是您可以根据需要添加或删除更多的适配器。此外,在构建实验室时,可以根据您的喜好指定子网。表 3-4 描述了三种虚拟网络模式和一种用户(定制)模式。 Virtual Network Adapter Modes Explained 默认情况下,在桥接网络适配器模式下,所有虚拟机共享主机的网络连接。默认情况下,桥接网络使用 VMware Workstation 中的 VMnet0,所有计算机使用相同的网关,因此主机使用相同的 DHCP 和 DNS 服务器。主机和虚拟机都通过相同的默认网关地址获取相同的子网 IP 地址。图 4-11 描绘了 VMware Workstation Pro 上的典型桥接网络。 ![img/492721_1_En_4_Fig16_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig16_HTML.jpg) 图 4-11。 VMware 网络适配器,桥接模式 纯主机模式非常适合测试虚拟环境中隔离网络中的两个或更多虚拟机。默认情况下,仅主机模式连接到 VMnet1,虚拟机连接在同一个子网中,在实验网络之外不进行任何交互。您的主机和虚拟机仍然可以通过 VMnet1 进行通信。在图 4-12 中,注意工作站提供 DHCP 服务。 ![img/492721_1_En_4_Fig17_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig17_HTML.jpg) 图 4-12。 VMware 网络适配器,仅主机模式 默认情况下,网络地址转换(NAT)模式通过 VMnet8 连接。网络地址转换意味着虚拟机的内部 IP 地址和主机的外部 IP 地址是不同的。在外部网络中,您只能看到主机的子网地址。如果主机可以连接到互联网,它就可以与互联网通信。图 4-13 描述了 VMware Workstation Pro 上的 NAT 配置示例。 ![img/492721_1_En_4_Fig18_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig18_HTML.jpg) 图 4-13。 VMware 网络适配器,NAT 模式 在自定义网络中,您可以选择前面的模式之一,并根据您的情况进行自定义。它与 NAT 模式相同,但默认设置有一些变化。 我们鼓励您花足够的时间了解不同的虚拟网络模式,比较它们与真实物理网络的不同之处,然后想象您如何在 PoC 实验室中使用不同的模式。熟悉每种网络模式如何与您的主机和其他网络交互将有助于您建立一个可以测试更高级 IT 理论的实验室。从菜单中打开虚拟网络编辑器,花些时间探索设置,添加/删除新的虚拟机网络,以及自定义设置。在下一节中,您将更详细地查看虚拟机设置。 ![img/492721_1_En_4_Figp_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Figp_HTML.gif)为什么要进行概念验证?在 it 领域,缩写 PoC 代表*概念证明*,它意味着在给定的场景中证明现有的或新的 IT 概念(或问题)。PoC 实验实验室测试某个理论的工作方式与该技术的设计方式相同。为了更好地了解并超越您的同事,您必须在 PoC 实验室中花很多时间,因为您将获得比书本或现实生活场景所能教给您的更广泛的知识。通过学习理论并在 PoC 实验室中测试理论,您总能学到更多东西,因为您不会在真实的生产环境中遇到所有的理论或问题。 ### 揭示每个虚拟网络接口类型 让我们看看每一种类型。 #### 仅限主机的网络 在这种模式下,您的虚拟机与主机和同一子网中的其他虚拟设备共享一个专用网络。如果您的虚拟机不需要主机外部的通信,此模式非常有用。如果您使用默认的仅主机网络,所有虚拟机都将连接到 VMnet1,您可以将 VMnet1 视为一个虚拟网络交换机。在这种模式下,为了方便起见,内置的 DHCP 服务器会向连接到该网络的主机提供 IP 地址。图 4-14 到 4-18 显示了 VMnet1 是如何配置的,相关的配置设置可以在你的主机操作系统上找到。有关 VMware 网络概念的更多说明,请参考以下教程: [`https://rednectar.net/2011/07/20/vmware-interfaces-tutorial/`](https://rednectar.net/2011/07/20/vmware-interfaces-tutorial/) 。 图 4-14 显示分配给该网络的现成 VMnet1 子网是 192.168.65.0/24。当然,子网和 CIDR 可以根据需要进行更改。此外,还有一个禁用 DHCP 服务的选项,以便您可以在虚拟机的操作系统中手动分配 IP 地址。 ![img/492721_1_En_4_Fig19_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig19_HTML.jpg) 图 4-14。 虚拟网络编辑器,VMnet1 仅主机网络 图 4-15 显示了配置相关网络连接的虚拟机设置。 ![img/492721_1_En_4_Fig20_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig20_HTML.jpg) 图 4-15。 虚拟机设置,仅主机网络连接 图 4-16 显示了`netsh interface ip show addresses "VMware Network Adapter VMnet1"`命令的结果,显示了主机 PC 上 VMnet1 的配置设置。正如所料,分配给主机的 VMnet1 适配器的 IP 地址是 192.168.65.1/24,接口度量为 35。接口度量 35 意味着 DHCP 服务配置了此 IP 地址。 ![img/492721_1_En_4_Fig21_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig21_HTML.jpg) 图 4-16。 Windows 主机 PC、VMware 网络适配器 VMnet1 如图 4-17 所示,当您启动虚拟机并检查网络接口设置时,DHCP 服务器为您的虚拟机分配的 IP 将来自与您的 VMnet1 网络相同的子网。由于您还没有创建和安装任何虚拟机,您可以在完成第 5 和 6 章的 Linux 安装后检查该设置。注意分配给 Ubuntu VM 的接口名是 ens33Linux 机器的网络适配器名称取决于您的 Linux OS 版本和发行版类型。 ![img/492721_1_En_4_Fig22_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig22_HTML.jpg) 图 4-17。 虚拟机,ens33 网络适配器 参考前面的图 4-12 来形象化并帮助您理解仅主机网络模式。现在,您知道在哪里可以找到虚拟机的纯主机虚拟网络信息。接下来,我们来看看桥接模式。 #### 桥接网络 在桥接模式下,虚拟机的行为就好像它是主机所在网络上的另一台计算机,因此虚拟机从真实网络接收 IP 地址。对于默认桥接模式,会分配 VMnet0 网络。如果您使用家庭网络连接到互联网,您的家庭网络路由器通常会提供 DHCP 服务。默认情况下,ISP 提供的互联网调制解调器/路由器的 IP 子网为 192.168.0.0/24 或 192.168.1.0/24,默认网关采用网络上的第一个 IP 地址(. 1)和最后一个 IP 地址(. 254)。在桥接模式下,其他物理计算机可以与您的虚拟机通信,您也可以从一台计算机模拟多台计算机。默认情况下,在桥接网络模式下,您的虚拟机可以通过物理方式与互联网通信,也可以与网络上的虚拟机通信。 图 4-18 显示了虚拟网络编辑器中的默认桥接网络。IP 地址将从主机的物理适配器网络上的真实网络设备分配,因此 DHCP 选项在设置下是灰色的。 ![img/492721_1_En_4_Fig23_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig23_HTML.jpg) 图 4-18。 虚拟网络编辑器,VMnet0 桥接网络 图 4-19 显示了配置和使用桥接网络的虚拟机设置。 ![img/492721_1_En_4_Fig24_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig24_HTML.jpg) 图 4-19。 虚拟机设置,桥接网络连接 图 4-20 揭示了来自 DHCP 服务器 192.168.0.1 的主机物理适配器网络配置;在典型的家庭网络中,默认网关兼作 DHCP 服务器。我的主机通过无线连接到互联网,因此 Wi-Fi 3 接口显示为适配器名称,但您的设置将与图 4-20 不同。 ![img/492721_1_En_4_Fig25_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig25_HTML.jpg) 图 4-20。 Windows 主机 PC、本地 PC 的互联网适配器(Wi-Fi) 图 4-21 显示了一个虚拟机终端会话,揭示了从家庭网络的 DHCP 服务器接收到的 IP 地址。另外,参考上图 4-11 来形象化并帮助你理解桥接网络模式。 ![img/492721_1_En_4_Fig26_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig26_HTML.jpg) 图 4-21。 虚拟机,ens33 网络适配器 #### 网络地址转换网络 在网络地址转换(NAT)模式下,虚拟机共享主机的 IP 和 MAC 地址。外部网络(Internet)将您的网络视为单一网络身份,内部网络在网络外部不可见。通常,在传统意义上,NAT 用于对外隐藏内部网络,并扩展网络中有用的 IPv4 IP 地址的数量。如果您已经学习了思科 CCNA 路由和交换,了解 NAT 或 PAT 是学习课程的一部分,因此了解这种类型的网络模式也有直接的意义。在本书中,您将在整个实验配置中利用 NAT 的功能,将网络配置保持在最低水平。 让我们看看图 4-22 并回顾一下在 VMware Workstation Pro 配置下 NAT 是如何配置的。在 NAT 模式下,VMware Workstation 提供 DHCP 服务,这意味着在网络内部使用另一个子网;在这种情况下,分配给 NAT 的子网位于 192.168.183.0/24 范围内,这是我们实验室连接到 Internet 和主机的主要方法。 ![img/492721_1_En_4_Fig27_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig27_HTML.jpg) 图 4-22。 虚拟网络编辑器,VMnet8 NAT 适配器 点击图 4-21 中的 NAT 设置按钮;它揭示了下面的 NAT 或 VMnet8 设置。您会注意到该子网的网关 IP 地址是 192.168.183.2,我们可以假设 192.168.183.1 已经分配给主机 PC 上的 VNnet8 适配器;这将在图 4-23 中得到验证。 ![img/492721_1_En_4_Fig28_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig28_HTML.jpg) 图 4-23。 虚拟网络编辑器,NAT 设置 图 4-24 显示了一个虚拟机的网络连接配置。 ![img/492721_1_En_4_Fig29_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig29_HTML.jpg) 图 4-24。 虚拟机设置,NAT 网络连接 图 4-25 显示了 DHCP 服务器的 IP 地址配置;如前所述,192.168.183.1/24 被分配给主机上的 VMnet8。但是,虚拟机的默认网关是 192.168.183.2。 ![img/492721_1_En_4_Fig30_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig30_HTML.jpg) 图 4-25。 Windows 主机、VMware 网络适配器 VMnet8 正如预期的那样,VMware Workstation Pro 中 NAT 网络上的虚拟机将分配来自同一 192.168.183.0/24 网络的 IP 地址。在图 4-26 中,192.168.183.129/24 已被分配给 Linux 服务器的 ens33 网络接口。参考上图 4-13 可以帮助你更好的理解 NAT 模式。 ![img/492721_1_En_4_Fig31_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_4_Fig31_HTML.jpg) 图 4-26。 虚拟机,ens33 网络适配器 ## 摘要 虚拟化技术使云服务成为可能,因此我们讨论了当前虚拟化技术的类型 1 和类型 2 虚拟机管理程序之间的差异。本章还向您介绍了 VMware Workstation 15 Pro,以及我们为什么使用该解决方案而不是其他解决方案。我们还查看了该软件在 Windows PC 上的基本操作,并深入研究了用户可以用来构建虚拟实验室环境以供工作和休闲使用的不同网络模式。现在,您已经了解了 VMware Workstation 的基本功能,是时候下载、安装和创建虚拟机了,即 Ubuntu 20.04 LTS(第五章)和 CentOS 8.1 服务器(第六章)。 # 五、创建 Ubuntu 服务器虚拟机 在本章中,您将下载最新的 Ubuntu Server 20 可引导映像,以便在 VMware Workstation 实例上安装和创建虚拟机。Ubuntu 是 Debian Linux 衍生发行版,在 Linux 上提供了最好的用户体验。对于大多数 Windows 用户来说,Ubuntu Desktop 将是第一个 Linux 入口。然而,对于这本书,我们将使用 Ubuntu Server 20 来代替,并稍作改动:我们将选择安装一个 UI 来增加我们实验室的灵活性。在本章结束时,您将能够从头开始创建一个 Ubuntu Server 虚拟机,定制操作系统上的选择性设置,拍摄虚拟机的快照,以及克隆虚拟机 ![img/492721_1_En_5_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Figa_HTML.jpg) 您可以使用不同操作系统的可引导映像文件创建虚拟机(VM ),并加载他人预先准备的 VM 映像。如果您选择第二个选项并导入或转换预制的虚拟机,您将错过学习如何从头构建虚拟服务器的机会。无论如何,对于功能更强大的台式机和笔记本电脑,从头构建和安装一个虚拟机并不需要花费太多时间,因此您将会得到从下载映像文件到进行基本配置的整个安装过程的指导。尽管本书中的虚拟机是功能更强大、可扩展的企业和云基础架构及网络解决方案的简化版本,但虚拟机配置概念和技术是相同的。 在过去的五年里,IT 界的流行词是*云计算*。在其最基本的形式中,云计算将大量强大的服务器压缩到集中或分散的数据中心,通过互联网提供按需服务。一切都在外部,用户通过瘦客户机或运行在远程节点上的应用进行连接。云计算机器的底层是虚拟化技术,理解虚拟化意味着您可以向云服务迈进一步。 在您的笔记本电脑上安装 VMware Workstation 15 Pro 后,您需要为您的首选操作系统下载一个可启动的映像文件来构建虚拟机。这个映像可以是任何风格的 Windows 或 Linux 映像。我们的意图很清楚,我们想摆脱 Windows 操作系统,适应 Linux 操作系统,所以我们的选择是显而易见的。我们将下载 Linux 可安装的映像并开始安装。由于我们还想知道两个最流行的 Linux 发行版之间的区别,即 Ubuntu 和 CentOS,我们将同时使用它们。Ubuntu 是最流行的基于 Debian 的 Linux 发行版之一,CentOS 是最广泛使用的基于 Red Hat 的免费发行版。为了让您对这两个 Linux 发行版有更多的实践经验,本章将安装 Ubuntu Server,然后在下一章安装 CentOS server。这两台服务器将在本书中灵活使用。两个 Linux 发行版之间的基本内核几乎是相同的。尽管如此,如果您同时使用它们,还是可以了解到一些细微的差异,因此安装两者并尝试学习基本的和一般的管理任务将会为您的学习和职业生涯增加更多的价值。 在本章的剩余部分,您将集中精力安装最新的 Ubuntu,它将被用作主要的 Python 网络自动化服务器。当你准备好了,让我们创建一个 Ubuntu Server 20.04 LTS 虚拟机。 ![img/492721_1_En_5_Figb_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Figb_HTML.gif)我们在本书中使用了最新的 Ubuntu Server 20.04 LTS 和 CentOS 8.1,但在生产环境中,系统管理员通常会选择使用最新软件的 n–1 版本。n–1 软件概念是使用以前的软件版本来避免错误和软件兼容性问题,目标是五个 9(每年 99.999%的正常运行时间)。此外,如果 n–1 版本没有提供您想要的性能或服务,您可以灵活地升级一个版本。 如果您打算在生产中运行这些服务器,请考虑使用 Ubuntu 18.04 LTS 或 CentOS 7.5 以获得更好的软件兼容性。这对于 CentOS 8 来说更有意义,因为它只支持到 2021 年底,而 CentOS 7.5 仍然支持到 2024 年。你将在第六章中了解更多。 ## 下载并安装 Ubuntu Server 20 镜像 要安装最新的 Ubuntu 服务器,首先需要从 Ubuntu 官方下载网站下载一个可启动的 Ubuntu 服务器镜像(`.iso`)文件。确保您有可靠的互联网连接进行下载,并按照下一节中的说明进行操作。 LTS:名字有什么意义? 传统上,Ubuntu Linux 服务器版本以 YY 命名。发行年份和月份的 MM 格式。所以,Ubuntu Server 20.04 表明它于 2020 年第四个月首次在市场上发布。LTS 代表长期支持,并表示该软件版本的支持时间为五年,而不是测试版。这与 2018 年第四个月发布的 Ubuntu 18.04 LTS 的命名惯例相同。 ## 下载 Ubuntu Server 20.04 LTS 镜像 | # | 工作 | | --- | --- | | 01 | 打开你最喜欢的网络浏览器,然后进入 Ubuntu 下载页面。预期文件大小约为 908MB。[`https://www.ubuntu.com/download/server`](https://www.ubuntu.com/download/server)Download the Ubuntu server version, which is the 64-bit PC (AMD64) server install image; the downloaded installable filename is `ubuntu-20.04-live-server-amd64.iso`. See Figure 5-1.![img/492721_1_En_5_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig1_HTML.jpg)图 5-1。Ubuntu Server 20.04 LTS 图像下载页面 | | 02 | 要检查下载的 ISO 文件是否已正确下载,请在安装前使用 WinMD5 或类似工具检查 MD5 值。见图 5-2 。WinMD5 can be downloaded for free from here:![img/492721_1_En_5_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig2_HTML.jpg)图 5-2。Ubuntu Server 20.04 ISO MD5 校验和验证[`www.winmd5.com/`](http://www.winmd5.com/) 或 [`www.winmd5.com/download/winmd5free.zip`](http://www.winmd5.com/download/winmd5free.zip)Ubuntu Server 20 LTS MD5 值可在以下链接中找到:[`https://releases.ubuntu.com/20.04/MD5SUMS`](https://releases.ubuntu.com/20.04/MD5SUMS)您将看到类似这样的内容:MD5 值:f03 d331 c 11136 e 24 C10 c 705 b 7 B3 EFC 39 f 文件名:* Ubuntu-20.04-live-server-amd64 . iso | ## 安装 Ubuntu Server 20.04 LTS 版 在 VMware Workstation 上安装 Linux 虚拟机很容易,但是您必须学习如何使用不同的可引导 ISO 映像创建和安装各种虚拟机。Ubuntu Server 20.04 的安装过程如下: | # | 工作 | | --- | --- | | 01 | First, start VMware Workstation 15 Pro using the shortcut icon on the host PC (Windows 10). See Figure 5-3.![img/492721_1_En_5_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig3_HTML.jpg)图 5-3。VMware 工作站快捷图标 | | 02 | Then select the File menu and then choose New Virtual Machine, as shown in Figure 5-4.![img/492721_1_En_5_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig4_HTML.jpg)图 5-4。创建新的虚拟机 | | 03 | When the New Virtual Machine Wizard window appears, leave “Typical (recommended)” selected and click the Next button at the bottom. See Figure 5-5.![img/492721_1_En_5_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig5_HTML.jpg)图 5-5。新建虚拟机向导 | | 04 | In the next window, select “Installer disk image file (iso)” and click the Browse button on the right. See Figure 5-6.![img/492721_1_En_5_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig6_HTML.jpg)图 5-6。Ubuntu 安装,选择安装介质 | | 05 | When the Browse for ISO image window appears, navigate to the `Downloads` folder where you have downloaded and saved the Ubuntu Server 20.04 installable file and select it. Click the Open button at the bottom. See Figure 5-7.![img/492721_1_En_5_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig7_HTML.jpg)图 5-7。Ubuntu 安装,选择可安装映像 | | 06 | When you return to the previous screen, click the Next button. See Figure 5-8.![img/492721_1_En_5_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig8_HTML.jpg)图 5-8。Ubuntu 安装,选定的可安装映像 | | 07 | Enter your name, user ID, and password and click the Next button. See Figure 5-9.![img/492721_1_En_5_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig9_HTML.jpg)图 5-9。Ubuntu 安装,输入用户凭证 | | 08 | Choose a meaningful server name for your Ubuntu server. This example is using the default folder, but if you want to store and run your VMs from another folder, you can change the settings now. `ubuntu20s1` is the server name given to my Ubuntu Server 20 server. See Figure 5-10.![img/492721_1_En_5_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig10_HTML.jpg)图 5-10。Ubuntu 安装,虚拟机文件夹选择 | | 09 | By default, 20GB will be allocated to your VM. I have enough disk space on my SSD, so I will increase this to 30GB for future use. Also, I prefer to store the virtual disk file as a single file as this makes migration and VM conversions easier. Click the Next button. Since thin provisioning is used, the entire 30GB of disk space is not used, but the disk size will gradually increase on demand while using up the VM’s disk space. See Figure 5-11.![img/492721_1_En_5_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig11_HTML.jpg)图 5-11。Ubuntu 安装、磁盘大小分配和单一虚拟磁盘文件选项 | | Ten | On the next screen, click the Finish button with the default setting “Power on this virtual machine after creation” selected. See Figure 5-12.![img/492721_1_En_5_Fig12_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig12_HTML.jpg)图 5-12。安装 Ubuntu,完成虚拟机配置 | | Eleven | The new VM will use the Ubuntu boot image to begin the Ubuntu Server installation on the first screen. See Figure 5-13.![img/492721_1_En_5_Fig13_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig13_HTML.jpg)图 5-13。Ubuntu 安装,服务器安装的第一个屏幕 | | Twelve | *Language selection*: You are prompted to select a language. English is selected as the default language. If you speak another language, select your native language from the selection. See Figure 5-14.![img/492721_1_En_5_Fig14_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig14_HTML.jpg)图 5-14。Ubuntu 安装,默认语言选择 | | Thirteen | *Keyboard configuration*: Leave the keyboard configuration at the default settings, highlight the Done button, and hit the Enter key. See Figure 5-15.![img/492721_1_En_5_Fig15_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig15_HTML.jpg)图 5-15。Ubuntu 安装,键盘选择 | | Fourteen | *Network connections*: If you did change the default network settings during VM creation, your VM will use a Network Address Translation (NAT) network. The first available IP address will be assigned to your VM from VMware Workstation’s NAT DHCP subnet pool, and the default IP range is from 192.168.183.128 to 192.168.183.254\. If you changed the subnet, your IP subnet and IP address range will differ from the example shown in this book. If your NAT network’s DHCP service has not been disabled, you will receive a valid IP address similar to Figure 5-16.![img/492721_1_En_5_Fig16_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig16_HTML.jpg)图 5-16。Ubuntu 安装,网络连接 | | Fifteen | *Configure proxy*: No proxy server is used on the current network, so there is no need to specify a proxy IP address. However, if you connect to the Internet via your company’s proxy server at work, then add the proxy information here. Of course, this information can be changed later. Assuming that you are also connecting via your home network, leave this blank, and let’s move on. See Figure 5-17.![img/492721_1_En_5_Fig17_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig17_HTML.jpg)图 5-17。Ubuntu 安装,配置代理 | | Sixteen | *Configure Ubuntu archive mirror*: Leave the default selection for this section, then highlight the Done key, and hit the Enter key. See Figure 5-18.![img/492721_1_En_5_Fig18_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig18_HTML.jpg)图 5-18。Ubuntu 安装,配置 Ubuntu 归档镜像 | | Seventeen | *Guided storage configuration*: Confirm that the `/dev/sda` size is the same as what you have allocated, then highlight the Done key, and hit the Enter key again. See Figure 5-19.![img/492721_1_En_5_Fig19_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig19_HTML.jpg)图 5-19。Ubuntu 安装,引导存储配置 | | Eighteen | *Storage configuration*: Once again, review and confirm the configuration, then highlight Done, and hit the Enter key on your keyboard to move to the next screen. See Figure 5-20.![img/492721_1_En_5_Fig20_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig20_HTML.jpg)图 5-20。Ubuntu 安装,存储配置 | | Nineteen | *Storage configuration*: In the “Confirm destructive action” box, highlight the Continue button and hit the Enter key to complete storage configuration. See Figure 5-21.![img/492721_1_En_5_Fig21_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig21_HTML.jpg)图 5-21。Ubuntu 安装、存储配置(确认破坏性操作) | | Twenty | *Profile setup*: Fill in your profile with a unique user ID and password. Make the server’s name the same as what you have specified during VM creation; in this case, it is ubuntu20s1 for Ubuntu Server 20 1\. Highlight the Done key and hit the Enter key again. See Figure 5-22.![img/492721_1_En_5_Fig22_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig22_HTML.jpg)图 5-22。Ubuntu 安装,设置配置文件 | | Twenty-one | *SSH Setup*: You will connect to this server via an SSH connection, so installing the OpenSSH server for an SSH connection is recommended. We can install this service after the OS installation, but it makes sense to install the SSH server now rather than later. Use the spacebar to select the “Install OpenSSH server” option, then highlight the Done key, and hit the Enter key again. See Figure 5-23.![img/492721_1_En_5_Fig23_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig23_HTML.jpg)图 5-23。Ubuntu 安装,设置 SSH | | Twenty-two | *Featured Server Snaps*: If you plan to use any of the services immediately after the installation, make your selections using the spacebar. Docker and PowerShell have been selected in Figure 5-24, as they are often useful services on any Linux server. Once all the selections are made, then highlight the Done key and hit the Enter key on your keyboard.![img/492721_1_En_5_Fig24_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig24_HTML.jpg)图 5-24。Ubuntu 安装,特色服务器快照 | | Twenty-three | *Installation complete*: Once you get to the final installation screen, click the Reboot option, and press the Enter key to restart your first VM. See Figure 5-25.![img/492721_1_En_5_Fig25_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig25_HTML.jpg)图 5-25。Ubuntu 安装,安装完成 | | Twenty-four | After the reboot, your screen will look similar to Figure 5-26.![img/492721_1_En_5_Fig26_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig26_HTML.jpg)图 5-26。Ubuntu 操作重新启动,初始屏幕 | | Twenty-five | Now use your user ID and password to log into your Ubuntu VM. Now your server is ready to serve you, but how well you will use this server in your study is entirely up to you. See Figure 5-27.![img/492721_1_En_5_Fig27_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig27_HTML.jpg)图 5-27。Ubuntu 安装,第一个登录屏幕 | ## 通过 SSH 登录到新的 Ubuntu 服务器 20 您很快就会发现,通过 VMware Workstation 的控制台屏幕访问新虚拟机非常麻烦。管理我们的服务器的一个更简单的方法是通过 SSH 连接来连接到这个服务器,从我们的 Windows 主机 PC 使用 SSH 客户端软件。这就像系统管理员如何管理他们网络上的系统一样。假设你已经在“网络连接”部分的 Ubuntu 服务器安装过程中记住了服务器分配的 IP 地址。在这种情况下,你有一个敏锐的头脑,但如果你忘记了分配的 IP 地址,让我们找到 IP 地址,这样我们就可以 SSH 到服务器。 | 01a | The first and most straightforward way to find the IP address on an Ubuntu is to issue an IP address or `IP adds` command. A NATted IP address from the VM NAT network should be assigned to your ens33 network adapter. The server in this example received the 192.168.183.129/24 IP address. Your server’s allocated IP address does not have to match, but if you have a valid IP address, that is a good indicator that your VM’s network is working correctly. If you received an IP address from a different subnet or have not received a valid IP address, then you must go back and review your VM network editor or VM network settings. See Figure 5-28.![img/492721_1_En_5_Fig28_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig28_HTML.jpg)图 5-28。Ubuntu 操作,使用 ip add 查找服务器 IP 地址 | | 01b | The second and more traditional way to find the IP address on any Linux server is to issue the `ifconfig` command . If you are only starting to transition to Linux OS from Windows OS, you will immediately notice that this command is like an `ipconfig` command at the Windows command prompt. On Ubuntu Server 20.04, we need to install `net-tools` first to enable this command. Run the `sudo apt install net-tools` command to install `net-tools`; this is also an excellent test for your Internet connectivity from the virtual Ubuntu server. See Figure 5-29.![img/492721_1_En_5_Fig29_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig29_HTML.jpg)图 5-29。Ubuntu 操作,net-tools 安装启用 ifconfig 命令 Once the installation has been completed, issue the `ifconfig` command to check the IP address. Because we installed the Docker service during the Ubuntu installation, the tool even picked up the `docker0` interface and is displaying it in Figure 5-30. But here, we are only interested in the information from the ens33 network adapter, and as expected, an IP address of 192.168.183.129/24 is given to the ens33 adapter with a broadcast address of 192.168.183.255.![img/492721_1_En_5_Fig30_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig30_HTML.jpg)图 5-30。Ubuntu 操作,使用 ifconfig 查找服务器 IP 地址 | | 02 | Stop here for one moment and think! The gateway IP for the 192.168.183.0/24 subnet is 192.168.183.2, not 192.168.183.1\. 192.168.183.2 is the NAT gateway IP, and 192.168.183.1 is the assigned IP address of the Windows 10 host’s VMnet8 (VMware Network Adapter VMnet8) interface. See Figure 5-31.![img/492721_1_En_5_Fig31_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig31_HTML.jpg)图 5-31。Ubuntu 操作,确认 Linux 上的默认网关 You can confirm this information from the Ubuntu server console by running `ip route | grep ^default` (or `ip r | grep ^def`). The meaning of this command is to look at the IP route with a general regular expression (`grep`) and find a string that begins with `^` and the word `default`. See Figure 5-32.![img/492721_1_En_5_Fig32_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig32_HTML.jpg)图 5-32。Ubuntu 操作,确认 VMware 工作站上的默认网关 Run the `netsh interface ip show addresses "VMware Network Adapter VMnet8"` command from your Windows 10 host PC. See Figure 5-33.![img/492721_1_En_5_Fig33_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig33_HTML.jpg)图 5-33。Ubuntu 操作,VMnet8 主机 IP 地址 | | 03 | Now, if you have not yet downloaded PuTTY from the Internet, go to PuTTY's home page ([`https://www.putty.org/`](https://www.putty.org/) ) and download a copy of PuTTY. Launch PuTTY from your Windows 10 host and then type in Ubuntu Server’s IP address with port 22 for the SSH connection. Click the Open button at the bottom-right corner of the PuTTY Configuration window. See Figure 5-34.![img/492721_1_En_5_Fig34_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig34_HTML.jpg)图 5-34。Ubuntu 操作,SSH 客户端从 PuTTY 登录 | | 04 | The server will send the server’s rsa2 key fingerprint to your PuTTY (SSH) session. Click Yes to proceed to the server’s login screen. See Figure 5-35.![img/492721_1_En_5_Fig35_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig35_HTML.jpg)图 5-35。Ubuntu 操作,接受来自服务器的 PuTTY 安全警报 | | 05 | Enter your username and password and hit the Enter key on your keyboard to log into your Ubuntu server. Now you are connected to your server via SSH. See Figure 5-36.![img/492721_1_En_5_Fig36_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig36_HTML.jpg)图 5-36。Ubuntu 操作,SSH 服务器登录 | | 06 | Customize the PuTTY configuration to suit your preferences. In Figure 5-37, I have changed the font and background color of my PuTTY console session and then saved the settings.![img/492721_1_En_5_Fig37_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig37_HTML.jpg)图 5-37。Ubuntu 操作,油灰背景和字体颜色变化 | ## 自定义 Ubuntu 服务器 创建你的 Ubuntu Server 20 LTS 虚拟机后,它开箱即用;然而,Ubuntu 默认禁用了一些关键设置。我们希望获得这些设置的完全访问权,以便我们的测试实验室增加更多的灵活性。我们可以调整一些东西,使这个服务器更加用户友好,为您的学习增加更多的价值。如果您在生产环境中运行 Ubuntu 服务器,请不要启用这些设置,因为它们不是最佳做法。由于这是我们的实验室虚拟机,我们将执行一些系统定制。首先,从远程客户端启用`root user ssh login`;这允许您以 root 用户身份通过 SSH 连接到该服务器,从而节省系统管理时间。第二,安装一个桌面 GUI 这将允许您登录到桌面设置,然后将其用作标准的最终用户机器,并通过 Linux GUI 使用可用的应用。第三,启用根用户 GUI 访问。此功能在默认情况下也是禁用的,但是可能需要 root 用户 GUI 访问服务器才能快速排除故障。 ### Ubuntu VM 定制 1:在 Ubuntu Server 20.04 上启用根用户 SSH 登录 一般来说,出于安全原因,大多数最新的 Ubuntu 服务器不允许 root 用户登录终端控制台或通过 SSH 登录。尽管如此,这种安全特性在您的实验室场景中经常会降低您的速度,要求您在键入的每个命令前输入`sudo`,并拒绝您在实验室中灵活工作的权限。因此,我们可以允许 SSH 登录,为您的实验室增加一点灵活性。但是,出于安全考虑,在实际生产环境中,最好禁用根控制台和 SSH 登录。首先,要启用直接根用户 SSH 访问,以您自己的身份登录到您的服务器,并运行命令`sudo passwd`。键入您的密码,系统会立即提示您输入新的 UNIX 密码。在此输入密码两次将启用 root 用户密码。星号用于说明目的,但密码在您键入时将被隐藏。 | # | 工作 | | --- | --- | | 01 | 通过执行以下任务启用 root 用户密码:`pynetauto@ubuntu20s1:~$ sudo passwd``sudo password for pynetauto: **********``Enter new UNIX password:********``Retype new UNIX password:********``passwd: password updated successfully` | | 02 | 前面的操作不会自动允许 SSH 登录到服务器;这需要通过更新 SSH 服务器配置文件来手动启用。要启用 root 用户 SSH 登录,请执行以下步骤:`pynetauto@ubuntu20s1:~$ sudo nano /etc/ssh/sshd_config`■注意,如果您在此阶段以 root 用户身份登录,则不必在命令前添加`sudo`。如果你一直在使用旧版本的 Ubuntu,首先你可能需要安装 nano 文本编辑器或者使用 vi 作为你的文本编辑器。 | | 03 | 以`#PermitRootLogin prohibit-password`开头的行阻止 root 用户 SSH 登录,我们必须取消对该行的注释以允许 root 用户 SSH 登录。为了简单起见和最佳实践,保留默认行不变,并添加一个新行,因为您可能需要将配置恢复到原始设置。在原始行后添加以下行并保存文件:`PermitRootLogin yes`See Figure 5-38.![img/492721_1_En_5_Fig38_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig38_HTML.jpg)图 5-38。Ubuntu 虚拟机定制 1,更改 sshd_config | | 04 | 要使之前的更改生效,您必须重新启动 SSH 服务器服务。`pynetauto@ubuntu20s1:~$` `sudo service ssh restart` | | 05 | Now from your Windows 10 host PC, launch PuTTY, enter the IP address of your server, and SSH in with port 22\. Once you successfully logged into your server via an SSH session, you will see a screen similar to Figure 5-39.![img/492721_1_En_5_Fig39_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig39_HTML.jpg)图 5-39。Ubuntu 虚拟机定制 1,根用户 SSH 登录 | 您可以使用一个单行命令向 root 用户授予 SSH 访问权限。只需在终端会话中运行该命令。 `sudo sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config` `[sudo] password for pynetauto: ********` ### Ubuntu 虚拟机定制 2:安装桌面 GUI 和其他软件包 或者,你可以安装一个桌面图形用户界面在 Ubuntu 服务器上运行。在测试实验室中,GUI 可以提供测试各种 IT 网络场景所需的额外功能,例如通过 web 浏览器测试 HTTPS 连接。在典型的生产环境中,为了提高服务器性能和安全性,Linux 通常被安装为一个没有 GUI 的无头服务器;Linux 服务器的 GUI 不是生产环境中的先决条件。无论如何,你应该知道如何在 Ubuntu Server 上安装 GUI,所以让我们学习如何从最初的 Ubuntu Server 20 终端控制台安装桌面 GUI 和其他软件包。 | # | 工作 | | --- | --- | | 01 | 首先,我们要在新安装的 Ubuntu Server 20.04 上安装`tasksel`。从 VMware 工作站控制台或 SSH 会话运行以下命令:`sudo apt install tasksel`See Figure 5-40.![img/492721_1_En_5_Fig40_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig40_HTML.jpg)图 5-40。Ubuntu 虚拟机定制 2,安装任务 | | 02 | 在`tasksel`安装完成后,你可以通过发出`sudo tasksel`命令来运行`tasksel`。这将启动 Ubuntu 包配置应用来选择和安装其他有用的应用;Ubuntu 桌面是这个包管理器下的一个选项。Select “Ubuntu desktop” and move to OK to start the desktop installation. See Figure 5-41.![img/492721_1_En_5_Fig41_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig41_HTML.jpg)图 5-41。Ubuntu 虚拟机定制 2,选择“Ubuntu 桌面”(GUI) | | 03 | Wait until the desktop installation is 100 percent complete, and then issue the `sudo reboot` command to restart your server. See Figure 5-42.![img/492721_1_En_5_Fig42_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig42_HTML.jpg)图 5-42。Ubuntu 虚拟机定制 2,Ubuntu 桌面安装 | | 04 | After the reboot , you will be prompted with the Ubuntu GUI login interface. Enter your login credentials and log into your server. See Figure 5-43.![img/492721_1_En_5_Fig43_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig43_HTML.jpg)图 5-43。Ubuntu 虚拟机定制 2,Ubuntu 桌面登录 | | 05 | You have successfully installed Ubuntu desktop on an Ubuntu Server. See Figure 5-44.![img/492721_1_En_5_Fig44_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig44_HTML.jpg)图 5-44。Ubuntu 虚拟机定制 2,Ubuntu 桌面 GUI | ### Ubuntu 虚拟机定制 3:启用根用户 GUI 访问 如果你有敏锐的眼光,你可能注意到了根用户 GUI 登录不起作用,并且你在质疑,我怎样才能启用根用户 GUI 登录?因为这是我们的实验室服务器,我们可以随意探索和定制,所以让我们启用 root 用户 GUI 登录。首先,您必须以普通用户(您自己)的身份登录到服务器来完成这个任务,或者可以选择从 PuTTY 通过 SSH 登录到服务器。 | # | 工作 | | --- | --- | | 01 | Open and edit `/etc/gdm3/custom.conf`, which is the GDM configuration file, using the nano text editor to allow root login. Add `AllowRoot=true`, as shown in Figure 5-45, and save the file.![img/492721_1_En_5_Fig45_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig45_HTML.jpg)图 5-45。Ubuntu VM 定制 3,更新 gdm3/custom.conf 文件 | | 02 | Next, edit the PAM authentication daemon configuration file at `/etc/pam.d/gdm-password` and comment on the specific line that denies root access to the graphical user interface. Use the nano or vi editor and open the `/etc/pam.d/gdm-password` file. Put `#` at the beginning of the line, which starts with `auth required pam_succeed_if.so user!= root quiet_success`, that you are commenting out, which means you are disabling the feature. See Figure 5-46.![img/492721_1_En_5_Fig46_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig46_HTML.jpg)图 5-46。Ubuntu VM 定制 3,更新 pam.d/gdm-password 文件 | | 03 | A mandatory reboot is required here once again. Reboot your Ubuntu server using the `sudo reboot` command or using the power button on the top-right corner of your Ubuntu desktop. Once the server has been restarted, log in as another user and enter **root** as the username and enter the root user’s password. See Figure 5-47.![img/492721_1_En_5_Fig47_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig47_HTML.jpg)图 5-47。Ubuntu 虚拟机定制 3,根用户 GUI 登录 | | # | 工作 | | 04 | Congratulations! You can log into Ubuntu desktop as a root user. See Figure 5-48.![img/492721_1_En_5_Fig48_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig48_HTML.jpg)图 5-48。Ubuntu VM 定制 3,Ubuntu 桌面 root 用户登录 | ## 拍摄虚拟机的快照 将操作系统作为虚拟机运行的最强大功能之一是快照的强大功能。在采用基于 ESXi 和 vCenter 的解决方案的真实生产环境中,拍摄快照和克隆是防止系统故障和从故障中恢复的有效方法。在 VMware 技术领域,其他系统恢复功能包括高可用性、vMotion 和 DRS。但是在 VMware Workstation Pro 解决方案中,我们使用快照和克隆来保存虚拟机的当前状态。快照就像给系统拍照一样,因此您可以将意外或失败的系统更改恢复到虚拟机的主要系统状态。克隆功能允许您对虚拟机进行部分或完整备份,以便在系统出现故障时保留系统状态。 对于 Python 网络自动化实验室,您可以选择使用快照或完整克隆作为故障安全恢复功能。快照将允许我们在实验过程中及时移动到不同的点。在服务器上安装其他软件之前,按照下面的任务制作 Ubuntu 服务器的快照。 | # | 工作 | | --- | --- | | 01 | To take a snapshot of your current server state, from your VMware Workstation menu, select VM ➤ Snapshot ➤ Take Snapshot. See Figure 5-49.![img/492721_1_En_5_Fig49_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig49_HTML.jpg)图 5-49。VMware 快照,导航到快照菜单 | | 02 | Enter the name and tasks performed as a description, and click the Take Snapshot button to create a snapshot, as shown in Figure 5-50.![img/492721_1_En_5_Fig50_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig50_HTML.jpg)图 5-50。VMware 快照,创建虚拟机快照 | | 03 | At the bottom-left corner of the window, the percentage of snapshot progress will be displayed. Once it reaches 100 percent, navigate to the Snapshot Manager from the menu, and confirm the snapshot. See Figure 5-51.![img/492721_1_En_5_Fig51_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig51_HTML.jpg)图 5-51。VMware 快照,确认快照 | ## 克隆虚拟机 现在你有了一个定制的 Ubuntu 虚拟服务器;如果您想创建另一个虚拟机,可以通过克隆同一个虚拟机来实现。与从头安装一个新虚拟机所花费的时间相比,克隆可以让您以闪电般的速度重新创建同一台机器。克隆还可以对当前虚拟机状态进行完整备份,并保留服务器状态。现在我们来看看这个过程。 | # | 工作 | | --- | --- | | 01 | To clone a VM, navigate to VM ➤ Manage ➤ Clone. (See Figure 5-52) Alternatively, you can also start the cloning process within the Snapshot Manager.![img/492721_1_En_5_Fig52_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig52_HTML.jpg)图 5-52。VMware 克隆 1,制作克隆 | | 02 | Leave the default settings and click the Next button. See Figure 5-53.![img/492721_1_En_5_Fig53_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig53_HTML.jpg)图 5-53。VMware 克隆 1,克隆源 | | 03 | Select “Create a full clone” and click the Next button. See Figure 5-54.![img/492721_1_En_5_Fig54_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig54_HTML.jpg)图 5-54。VMware 克隆 1,克隆类型,完整克隆 | | 04 | Rename the cloned VM and click the Finish button. See Figure 5-55.![img/492721_1_En_5_Fig55_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig55_HTML.jpg)图 5-55。VMware 克隆 1,重命名克隆的虚拟机 | | 05 | Now click the Close button. See Figure 5-56.![img/492721_1_En_5_Fig56_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig56_HTML.jpg)图 5-56。VMware 克隆 1,关闭虚拟机克隆向导 | | 06 | You will see another Ubuntu server added to your VM library. See Figure 5-57.![img/492721_1_En_5_Fig57_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig57_HTML.jpg)图 5-57。VMware 克隆 1,克隆的虚拟机添加到库中 | | 07 | Optionally, if you want to copy and duplicate your VM simply, simply copy the folder containing all the VM files to other storage. This method will take up a lot of hard disk space, so if you are using a smaller SSD, then a snapshot is a better option than fully cloning a VM. See Figure 5-58.![img/492721_1_En_5_Fig58_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_5_Fig58_HTML.jpg)图 5-58。VMware 克隆 2,将虚拟机文件夹复制到外部驱动器 | ## 摘要 安装一个 Ubuntu 服务器作为虚拟机是一个简单的过程,但是安装一个图形用户界面是一个选项。现在您已经成功地安装、定制、备份和克隆了一个新创建的 Ubuntu server VM,是时候创建另一个 Linux 服务器作为我们的第二台服务器了。在下一章中,您将按照类似的步骤为 CentOS 8.1 创建一个虚拟机,该虚拟机将作为我们网络自动化实验室的一体化 IP 服务服务器。 # 六、创建 CentOS 8 服务器虚拟机 在本章中,您将下载 CentOS 8 可引导映像,以便在 VMware 工作站上安装和创建第二个虚拟机。CentOS 8 服务器基于社区版本的 Red Hat Enterprise Linux 8 (RHEL8),这是 Linux 社区用户中最受欢迎的企业 Linux 操作系统之一,因为它具有稳定性和安全性。本章结束时,您将能够从头开始创建 CentOS 8 服务器虚拟机,并使用`nmtui`包自定义网络设置,从而为您的网络自动化实验室增加灵活性。在准备过程中,您将下载 GNS3 `.ova`模板文件,然后导入并创建一个 GNS3 VM,用于 Cisco IOS 和 IOSv 映像集成。 ![img/492721_1_En_6_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Figa_HTML.jpg) 在前一章中,您创建了一台 Ubuntu 服务器,并学习了如何定制它的设置,使它更适合我们的实验室。本章是本书中较短的章节之一,其中您将使用 CentOS 8 可引导映像创建另一个 Linux 虚拟机。虽然我们可以简单地克隆现有的 Linux VM 来创建更多的虚拟机,但是这阻止了我们学习其他 Linux 发行版的基本操作。因此,我们将下载并安装第二个不同的 Linux 发行版。在第八章中,您将了解如何将该服务器转变为具有多种 IP 服务的服务器,以进行实验室测试。与前面的过程一样,您将搜索并下载可引导映像,然后安装该映像并创建一个虚拟机。创建 CentOS 8 虚拟机后,您将通过 SSH 测试服务器的连接,并学习一种在 Linux 服务器上管理网络适配器的简单方法。为了准备 GNS3 集成,我们需要下载适用于 VMware Workstation 的 GNS3 虚拟机的正确副本,因此使用下载的 GNS3 虚拟机`.ova`文件,将提前创建 GNS3 虚拟机以实现更平稳的集成。让我们快速创建您的第二个虚拟机。 ## 下载并安装 CentOS 8 服务器映像 下载和安装 CentOS VM 的实际过程与 Ubuntu 20.04 服务器安装相同。与 Ubuntu 20.04 LTS 虚拟机安装一样,您必须首先下载可引导的 CentOS 8 安装映像(`.iso`)文件。首先,检查您的互联网连接,并按照本章中的步骤在 VMware Workstation 15 Pro 上创建和安装新的 CentOS 8 虚拟机。在写这本书的时候,最新的 CentOS 版本是 8.1.1804。当您阅读本书时,新版本将会推出,但只要您坚持使用主要的 8.x(或 7.x)版本,您将能够按照本书中的说明成功安装 CentOS server。如果您选择下载并安装另一个 CentOS 版本,安装步骤仍然几乎相同。在第八章中,您将安装不同的软件来将本服务器转变为多用途实验室服务器,因此您可能需要遵循针对您的 CentOS 系统版本建议的软件安装程序,因为在本 CentOS 版本上兼容的一个软件版本可能无法在之前或更新的软件版本上兼容。这款 CentOS 虚拟机还可用作多用途实验室服务器,运行多种 IP 服务,为未来的 IT 认证做准备。 随着 IBM 收购 Red Hat,CentOS 服务器的未来开发和支持发生了一些变化。对 CentOS 8 服务器的社区支持将于 2021 年 12 月 31 日停止,社区用户只能使用 CentOS 8 流或 CentOS 的更新流版本。CentOS Stream 的定位介于 Fedora(红帽企业 Linux 的 beta 版)和红帽企业 Linux 之间,所以我们可以说它是红帽企业 Linux 的早期发布版本。如果您在生产中使用 Stream,将会出现从 Red Hat (IBM)获得支持的问题,因为这消除了 Red Hat 支持不再受支持的 CentOS server 版本中的错误或问题的义务。然而,在实验室环境中使用流版本仍然是可以接受的,因为大多数函数将满足我们的测试需求。 首先,让我们从 CentOS 官方网站下载最新的 CentOS 8 安装 ISO 文件。 ### 下载 CentOS 8 服务器映像 | # | 工作 | | --- | --- | | 01 | 从您最喜欢的网页浏览器,前往 CentOS 的官方网站建议,并单击网页上的 x86_64 链接。或者,您可以选择下载 CentOS 流版本。见图 6-1 。URL: [`https://www.centos.org/download/`](https://www.centos.org/download/)![img/492721_1_En_6_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig1_HTML.jpg)图 6-1。CentOS 官方下载页面 | | 02 | 接下来,找到离您最近的 CentOS 图像下载服务器,并从 FTP 服务器下载最新的 CentOS 8 ISO。估计的文件大小会因您下载的文件而异,但这里您将下载以`–x86-64-dvd1.iso`结尾的文件,其大小超过 7GB。在写这本书的时候,最新可用的 CentOS 8 文件是`CentOS-8.1.1911-x86_64-dvd1.iso`。见图 6-2 。URL: [`http://isoredirect.centos.org/centos/8/isos/x86_64/`](http://isoredirect.centos.org/centos/8/isos/x86_64/)![img/492721_1_En_6_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig2_HTML.jpg)图 6-2。CentOS 8,官方下载页面镜像示例 | 软件下载完成并保存到您的`Downloads`文件夹后,您就可以从 VMware Workstation 15 Pro 创建 CentOS 虚拟机了。 ### 安装 CentOS 8 服务器 CentOS 8 服务器虚拟机的安装过程类似于 Ubuntu 服务器的安装过程,所以让我们快速浏览一下这个过程,并创建一个新的服务器虚拟机。 **#** **任务** 01 在您的 VMware 工作站菜单中,选择文件➤新建虚拟机。见图 6-3 。 ![img/492721_1_En_6_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig3_HTML.jpg) 图 6-3。 CentOS 8 安装,创建新的虚拟机 02 当“新建虚拟机向导”出现时,单击“典型(推荐)”选项,然后单击“下一步”按钮。见图 6-4 。 ![img/492721_1_En_6_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig4_HTML.jpg) 图 6-4。 CentOS 8 安装,新虚拟机向导起始页 03 当您回到图 6-5 所示的新建虚拟机向导窗口时,选择“我将稍后安装操作系统”选项。如果您在此选择“安装程序光盘镜像文件(iso)”选项,CentOS 8 镜像将返回`Pane is a dead error`,您将无法安装操作系统。见图 6-5 。 Note 请确保您在此处选择了“我将稍后安装操作系统”。 ![img/492721_1_En_6_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig5_HTML.jpg) 图 6-5。 CentOS 8 安装,选择“我将稍后安装操作系统”选项 04 根据您在主机上安装的 VMware Workstation 15 的版本,CentOS 8.x 版本可能不可用。如果您遇到这个问题,您可以随时选择您选择的红帽企业 Linux (REHL) 8 64 位版本,因为 CentOS 8 与 RHEL 8 是相同的版本,没有徽标和专有合同。见图 6-6 。 Note 注意,IBM 在 2019 年 7 月收购了红帽。CentOS 一直是当前生产红帽企业版 Linux 的社区版;当 RHEL 遇到一个 bug 时,Red Hat 在三天内为 CentOS 提供了一个修复。这使得 CentOS 像 RHEL Linux 一样可靠,但是用户仍然可以在生产中使用 CentOS。相比之下,CentOS Stream 是 RHEL 预发布的开发预览版或实验版,Fedora 领先于 CentOS Stream,甚至更具实验性。2020 年 12 月 9 日,红帽(IBM)宣布 2021 年底将不再支持 CentOS Linux 8。这不会影响您的实验室环境;但是,避免在生产中部署 CentOS 8。对于您的实验室构建,我们可以在 2021 年后使用 CentOS Stream 或 Fedora 来取代 CentOS 8。 ![img/492721_1_En_6_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig6_HTML.jpg) 图 6-6。 CentOS 8 安装,操作系统选择 05 在下一个窗口中,按照要求输入用户凭据,然后单击下一步。见图 6-7 。 ![img/492721_1_En_6_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig7_HTML.jpg) 图 6-7。 CentOS 8 安装,输入用户凭据 运用一些想象力,给你的服务器起一个容易识别的有意义的名字。该服务器命名为 CentOS8s1,参考 CentOS8 服务器 1。单击下一步按钮。参见图 6-8 。 ![img/492721_1_En_6_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig8_HTML.jpg) 图 6-8。 CentOS 8 安装、服务器名称和文件夹配置 07 分配所需的空闲磁盘空间;在下面的示例中,30GB 的磁盘空间分配给了 GUI,并将安装其他软件。默认情况下,磁盘设置为精简资源调配,逐渐增加磁盘大小,直到达到 30GB,这样初始磁盘空间就会少很多。此外,单击“将虚拟磁盘存储为单个文件”,以便于以后的文件管理。然后单击下一步。参见图 6-9 。 ![img/492721_1_En_6_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig9_HTML.jpg) 图 6-9。 CentOS 8 安装,分配磁盘容量以供使用 08 单击“Finish”按钮完成虚拟机配置。参见图 6-10 ![img/492721_1_En_6_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig10_HTML.jpg) 图 6-10。 CentOS 8 安装,完成配置 现在单击“编辑虚拟机设置”来更改 CD/DVD 选择。见图 6-11 。 ![img/492721_1_En_6_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig11_HTML.jpg) 图 6-11。 CentOS 8 安装,选择“编辑虚拟机设置” 10 单击左窗格中的 CD/DVD (SATA),单击“使用 ISO 映像文件”,然后单击浏览按钮。见图 6-12 。 ![img/492721_1_En_6_Fig12_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig12_HTML.jpg) 图 6-12。 CentOS 8 安装,选择虚拟机设置 11 选择已经下载到`Downloads`文件夹中的 CentOS 安装文件,然后点击打开。见图 6-13 。 ![img/492721_1_En_6_Fig13_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig13_HTML.jpg) 图 6-13。 CentOS 8 安装,选择 CentOS ISO 文件 12 现在,单击“启动该虚拟机”开始 CentOS 安装。见图 6-14 ![img/492721_1_En_6_Fig14_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig14_HTML.jpg) 图 6-14。 CentOS 8 安装,启动虚拟机 13 单击安装 CentOS Linux 8 开始安装。见图 6-15 ![img/492721_1_En_6_Fig15_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig15_HTML.jpg) 图 6-15。 CentOS 8 安装,选择安装选项 14 保留默认语言为英语(美国),然后单击继续进入下一页。见图 6-16 ![img/492721_1_En_6_Fig16_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig16_HTML.jpg) 图 6-16。 CentOS 8 安装,语言选择 15 在安装摘要窗口中,选择系统下的安装目的地,进入并选择操作系统安装盘。见图 6-17 ![img/492721_1_En_6_Fig17_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig17_HTML.jpg) 图 6-17。 CentOS 8 安装,安装摘要 16 突出显示“VMware 虚拟 NVMe 磁盘 30 GiB ”,然后单击“完成”按钮返回到上一个安装窗口。参见图 6-18 。 ![img/492721_1_En_6_Fig18_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig18_HTML.jpg) 图 6-18。 CentOS 8 安装,安装目标选择 17 接下来,单击“带 GUI 的软件选择服务器”在安装过程中安装额外的软件。参见图 6-19 。 ![img/492721_1_En_6_Fig19_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig19_HTML.jpg) 图 6-19。 CentOS 8 安装,软件选择 18 在软件选择窗口中,选择带 GUI 的服务器,并选择其他软件,如 FTP 服务器、邮件服务器等。这里,至少安装 FTP 服务器,为以后节省时间。其他 IP 服务将在下一章手动安装。一旦您对自己的选择感到满意,请单击“完成”按钮。 图 6-20 、图 6-21 和图 6-22 显示了您可以在软件选择下选择的附加软件。 ![img/492721_1_En_6_Fig22_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig22_HTML.jpg) 图 6-22。 CentOS 8 安装,软件选择 3 ![img/492721_1_En_6_Fig21_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig21_HTML.jpg) 图 6-21。 CentOS 8 安装,软件选择 2 ![img/492721_1_En_6_Fig20_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig20_HTML.jpg) 图 6-20。 CentOS 8 安装,软件选择 1 19 下一步单击网络和主机名。见图 6-23 ![img/492721_1_En_6_Fig23_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig23_HTML.jpg) 图 6-23。 CentOS 8 安装,安装摘要 20 默认情况下,CentOS 服务器的网络适配器是关闭的。单击“开”按钮打开网络适配器。一旦交换机通电,将自动从 NAT 的 DHCP 子网分配一个 IP 地址。图 6-24 收到一个 IP 地址 192.168.183.130/24。确认 DHCP 分配 IP 地址后,单击完成退出。见图 6-24 。 ![img/492721_1_En_6_Fig24_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig24_HTML.jpg) 图 6-24。 CentOS 8 安装,连接到网络 21 返回安装摘要窗口后,单击开始安装按钮开始操作系统安装。见图 6-25 ![img/492721_1_En_6_Fig25_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig25_HTML.jpg) 图 6-25。 CentOS 8 安装,开始安装 22 下载软件包时,您可以在进入安装模式之前尝试设置 root 用户的密码。单击 Root 密码。见图 6-26 ![img/492721_1_En_6_Fig26_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig26_HTML.jpg) 图 6-26。 CentOS 8 安装,设置 root 用户密码 23 输入两次 root 用户密码,然后单击完成按钮退出。见图 6-27 ![img/492721_1_En_6_Fig27_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig27_HTML.jpg) 图 6-27。 CentOS 8 安装,输入根用户密码 24 当您返回上一屏幕时,CentOS8 服务器安装将开始。现在,等待大约五分钟,安装完成。见图 6-28 ![img/492721_1_En_6_Fig28_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig28_HTML.jpg) 图 6-28。 CentOS 8 安装,正在安装服务器 25 CentOS 8 安装完成后,系统会提示您重新启动。单击 Reboot 重新启动新的 Linux 服务器。参见图 6-29 ![img/492721_1_En_6_Fig29_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig29_HTML.jpg) 图 6-29。 CentOS 8 安装,安装完成并重新启动 26 重新启动服务器后,您将看到初始配置屏幕。单击许可信息,同意最终用户许可协议,免费使用 CentOS。见图 6-30 ![img/492721_1_En_6_Fig30_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig30_HTML.jpg) 图 6-30。 CentOS 8 安装,初始配置窗口 27 单击“我接受许可协议”复选框,然后单击完成。见图 6-31 。 ![img/492721_1_En_6_Fig31_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig31_HTML.jpg) 图 6-31。 CentOS 8 安装,接受最终用户许可协议 28 当您返回初始设置窗口时,您会注意到许可信息字段中的警告标志已经消失。现在,单击“完成配置”按钮,首次登录 CentOS。见图 6-32 。 ![img/492721_1_En_6_Fig32_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig32_HTML.jpg) 图 6-32。 CentOS 8 安装,初始设置完成 29 单击下一步。见图 6-33 。 ![img/492721_1_En_6_Fig33_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig33_HTML.jpg) 图 6-33。 CentOS 8 安装,欢迎窗口 30 单击下一步按钮。见图 6-34 。 ![img/492721_1_En_6_Fig34_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig34_HTML.jpg) 图 6-34。 CentOS 8 安装,隐私定位服务 31 单击“跳过”按钮跳过在线帐户配置。见图 6-35 。 ![img/492721_1_En_6_Fig35_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig35_HTML.jpg) 图 6-35。 CentOS 8 安装,在线帐户 32 输入您的详细信息,然后单击下一步。见图 6-36 ![img/492721_1_En_6_Fig36_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig36_HTML.jpg) 图 6-36。 CentOS 8 安装,关于你的屏幕 33 接下来您将被带到密码屏幕。输入两次密码,然后单击“下一步”按钮。见图 6-37 。 ![img/492721_1_En_6_Fig37_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig37_HTML.jpg) 图 6-37。 CentOS 8 安装,设置密码 34 在准备就绪屏幕上,单击开始使用 CentOS Linux 完成配置。见图 6-38 ![img/492721_1_En_6_Fig38_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig38_HTML.jpg) 图 6-38。 CentOS 8 安装,准备就绪屏幕 35 输入您的密码,然后单击登录按钮。见图 6-39 ![img/492721_1_En_6_Fig39_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig39_HTML.jpg) 图 6-39。 CentOS 8 安装,首次登录 36 你现在会看到入门屏幕。现在关闭窗口并开始使用 CentOS 服务器是安全的。见图 6-40 ![img/492721_1_En_6_Fig40_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig40_HTML.jpg) 图 6-40。 CentOS 8 安装,密码配置 37 启动终端控制台等应用以使用服务器。见图 6-41 。 ![img/492721_1_En_6_Fig41_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig41_HTML.jpg) 图 6-41。 CentOS 8 安装,首次使用 ### 通过 SSH 登录到新的 CentOS 8 服务器 在 CentOS 8 上,您不需要更改任何设置就可以使用 SSH 连接远程连接到服务器;默认情况下,用户和 root 用户都可以使用 SSH 协议连接到服务器。不要相信我的话。让我们通过在您的主机 Windows 10 上打开 PuTTY 并通过 SSH 连接到主机 Windows 10 桌面服务器来快速验证这一点。 **#** **任务** 1 首先,从主机的 Windows 桌面打开 PuTTY,输入 CentOS 8 虚拟机的 IP 地址。此处显示的示例使用了 IP 地址 192.168.183.130,但是您的 IP 地址可能不同,这仍然没问题。单击打开按钮。见图 6-42 。 ![img/492721_1_En_6_Fig42_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig42_HTML.jpg) 图 6-42。 CentOS 8 SSH 登录,PuTTY SSH 登录到 CentOS 8 2 当提示 PuTTY 安全警报时,接受 rsa2 密钥,然后单击 Yes 按钮。见图 6-43 。 ![img/492721_1_En_6_Fig43_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig43_HTML.jpg) 图 6-43。 CentOS 8 SSH 登录,接受 rsa2 服务器密钥 3 现在使用您的用户 ID 和密码来确认 SSH 登录工作正常。见图 6-44 。 ![img/492721_1_En_6_Fig44_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig44_HTML.jpg) 图 6-44。 CentOS 8 SSH 登录,用户登录 4 或者,启动另一个 PuTTY 会话,这次以 root 用户身份登录。您没有进行任何系统更改,但是 CentOS 8 将允许您使用 SSH 协议连接到服务器。你会发现,就像 Ubuntu 一样,CentOS 有真正的潜力成为你最喜欢的 Linux 发行版系统之一。见图 6-45 。 ![img/492721_1_En_6_Fig45_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig45_HTML.jpg) 图 6-45。 CentOS 8 SSH 登录,根用户登录 这就完成了 CentOS 8 的安装和初始设置。在进入下一个话题之前,这里是我对 Linux 操作系统的一些想法。 互联网上的快速调查显示,与 Windows 操作系统相比,个人电脑用户更喜欢 Linux 操作系统的总比例只是很小的一部分;然而,在 IT 服务的企业层面,使用 Linux 操作系统的百分比几乎与微软 Windows 操作系统相等,Linux 系统的普及势头越来越大。如果你是一个刚进入 IT 行业的 IT 技术人员,这实际上是一个重要的消息,因为更多的机会提供给了那些能够驯服和掌握 Linux 作为他们在企业级首选操作系统的人。 我做网络工程师已经很多年了。我承认我对我的 Windows 桌面和服务器操作系统太过舒适了;我也因为拒绝打开思维开始学习 Linux 而感到内疚。当我开始自学 Python 时,这种情况发生了变化,因为我感到沮丧,并努力使用 Windows 操作系统做更多的事情。学习 Python 后,我觉得在 Linux OS 上使用 Python 实现网络自动化比 Windows OS 更容易;因此,如果你真的想把 Python 作为你的职业,开始学习 Linux 吧。对于使用 Python 的网络自动化,Linux 知识是先决条件,而不是选项。基于你丰富的网络知识和经验,同时学习 Python 和 Linux。 ### 在 Linux 上管理网络适配器 例如,如果您想要更改 Linux 机器上的网络设置,请为 CentOS 服务器提供一个静态 IP 地址,这样,在 DHCP 租约到期后,IP 地址不会更改。您可以使用`nmtui`工具轻松更改该设置。如果您在 Linux 安装过程中忘记打开网络适配器的电源,您可以使用这个工具来更改设置。从 CentOS 快速运行`sudo nmtui`命令。如果您以`sudo`用户的身份登录,您可以省略命令前面的`sudo`。 | # | 工作 | | --- | --- | | 01 | You can run the `nmtui` command from the VMware Workstation Console or PuTTY SSH session. Type in `sudo nmtui` and hit the Enter key on your keyboard. See Figure 6-46.![img/492721_1_En_6_Fig46_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig46_HTML.jpg)图 6-46。CentOS 8 配置,sudo nmtui 命令 | | 02 | When CentOS’s NetworkManager TUI appears, select “Edit a connection.” See Figure 6-47.![img/492721_1_En_6_Fig47_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig47_HTML.jpg)图 6-47。CentOS 8 配置,网络管理器 TUI | | 03 | Select ens160 and click Edit . See Figure 6-48.![img/492721_1_En_6_Fig48_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig48_HTML.jpg)图 6-48。CentOS 8 配置,选择网络适配器 | | 04 | Click the Automatic option on the right side of IPv4 Configuration and change it to Manual. Click the Show option on the right. See Figure 6-49.![img/492721_1_En_6_Fig49_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig49_HTML.jpg)图 6-49。CentOS 8 配置,选择手动配置 | | 05 | Now enter your server IP address and the NAT gateway’s IP address . Move down and click OK. See Figure 6-50.![img/492721_1_En_6_Fig50_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig50_HTML.jpg)图 6-50。CentOS 8 配置,保存手动更改 | | 06 | Press the Tab key to move down and select Back to return to the first TUI interface. See Figure 6-51.![img/492721_1_En_6_Fig51_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig51_HTML.jpg)图 6-51。CentOS 8 配置,背面选项 | | 06 | Click Quit to save your changes and exit the application . See Figure 6-52.![img/492721_1_En_6_Fig52_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig52_HTML.jpg)图 6-52。CentOS 8 配置,保存和退出 TUI | 在此阶段,考虑如何在实验室中使用该服务器并安装任何软件。一旦您对 CentOS 8.1 的当前状态感到满意,Linux 虚拟机定制就会在运行时拍摄虚拟机的快照,或者对关闭的虚拟机文件进行完整克隆。拍摄快照或克隆与之前的 Ubuntu 20 过程相同。在本章的后面,您将从一个`ova`文件创建第三个虚拟机,这是创建虚拟机的另一种方式。`ova`文件是 VMware Workstation 和 Oracle VM VirtualBox 使用的虚拟设备。一个`ova`文件是一个打包的文件,包含用于描述虚拟机的文件,其中包括一个`ova`描述符文件,可选的 manifest(。MF)和证书文件以及其他相关文件。换句话说,`ova`文件是另一个虚拟机管理员预先准备的预定义虚拟机模板,用于即时部署特定的服务器。您将从 GNS3 GitHub 网站下载一个 GNS3 VM `ova`文件,并通过导入该文件创建一个 GNS 3 VM;这是 GNS3 安装和集成的准备步骤,将在第十章中进行。因此,不要在这里跳过 GNS3 虚拟机的创建过程。 ## 通过导入一个。ova 文件 GNS3 VM `.ova`文件由社区开发者在 [www。gns3。org](http://www.gns3.org) ,用于创建一个虚拟机,托管运行在 GNS3 应用上的网络、安全和系统映像。该服务器是一台 Linux 服务器,旨在减轻 GNS3 物理主机的工作负载,因此仿真 GNS3 设备可以平稳运行,而不会影响底层物理主机。您很快就会发现,GNS3 VM 提供的不仅仅是运行路由器和交换机。它还可以托管容器化的 Linux 服务器等等。GNS3 和 GNS3 VM 是开源的,对每个人都是免费的。在本书中,我们在 Windows 10 上使用 VMware Workstation,因此我们需要下载在 Windows 上工作的 GNS3 VM 文件。在撰写本章时,最新的 GNS3 版本是 v2.2.8,此处将使用该版本,但请尝试下载最新版本的 GNS3 文件,因为较新的版本总是提供性能改进,并且许多错误已被消除。这里我们将使用 GNS3 v2.2.8,以便下载相应的`GNS3.VM.VMware.Workstation.2.2.8.zip`文件。系统会提醒您再次下载`GNS3-2.2.8-all-in-one.exe`文件,但您也可以现在下载以节省时间。 ### 从下载并安装 GNS3 虚拟机。ova 文件 下载 GNS3 虚拟机文件和创建 GNS3 虚拟机的过程如下: | # | 工作 | | --- | --- | | 01 | First, go to GNS3’s GitHub site specified here to download the GNS3 VM file. Download the latest version of the GNS3 VM file . The latest version is `GNS3.VM.VMware.Workstation.2.2.8.zip` at the time of writing this book. At the time of writing this book, the latest GNS VM release version is 2.2.8\. See Figure 6-53.![img/492721_1_En_6_Fig53_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig53_HTML.jpg)图 6-53。GNS3 和 GNS3 虚拟机,下载。GitHub 中的 ova 文件[`https://github.com/GNS3/gns3-gui/releases`](https://github.com/GNS3/gns3-gui/releases) | | 02 | Now go to your `Downloads` folder and unzip the downloaded file in the same location. See Figure 6-54.![img/492721_1_En_6_Fig54_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig54_HTML.jpg)图 6-54。解压缩 GNS3 虚拟机。ova 文件到一个文件夹 | | 03 | Now go back to your VMware Workstation 15 Pro and select the File ➤ Open menu. See Figure 6-55.![img/492721_1_En_6_Fig55_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig55_HTML.jpg)图 6-55。VMware 工作站菜单,文件➤打开 | | 04 | Locate the extracted GNS3 VM `.ova` file , select the file, and then click Open. See Figure 6-56.![img/492721_1_En_6_Fig56_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig56_HTML.jpg)图 6-56。VMware 工作站,GNS3 虚拟机。ova 文件打开 | | 05 | In the next window, click the Import button to import the GNS3 VM. You can leave the storage path as the default location. See Figure 6-57 and Figure 6-58.![img/492721_1_En_6_Fig57_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig57_HTML.jpg)图 6-57。VMware 工作站,GNS3 虚拟机导入![img/492721_1_En_6_Fig58_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig58_HTML.jpg)图 6-58。VMware 工作站,GNS3 虚拟机导入正在进行中 | | 06 | Now, GNS3 VM has been created on your VMware Workstation 15 Pro and ready for use. You do not have to do anything with this VM at this stage as GNS3 software installation and integration are required for this VM to become useful. See Figure 6-59.![img/492721_1_En_6_Fig59_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_6_Fig59_HTML.jpg)图 6-59。安装了 VMware 工作站、GNS3 虚拟机 | 这就完成了以后用于 GNS3 集成的 GNS3 VM 的下载、导入和安装。现在我们已经有了所有需要的虚拟机,我们可以学习 Linux 基础知识、Linux 上的 Python、GNS3 和 Python 网络自动化。现在学习平台差不多完成了。 ## 摘要 在本章中,您下载、安装并创建了 CentOS 8 虚拟机。您测试了新创建的 CentOS 虚拟机的 SSH 登录,并学习了使用`nmtui`包方法管理网络适配器设置的简单方法。最后,您添加了 GNS3 虚拟机,为以后的 Cisco IOS 集成做准备。现在您有了两种不同风格的 Linux 服务器;你将在第七章学习 Linux 基础知识,然后在第八章学习 Linux 软件安装和管理。 ## 故事时间 3:虚拟机管理程序的起源 之前,您学习了两种不同的虚拟化程序,它们被分为类型 1 或类型 2 虚拟机管理程序。由于硬件和软件,尤其是虚拟化技术的快速发展,围绕云和云计算以及随需应变 IT 概念的大肆宣传应运而生。如果你查阅 *hypervisor* 的字典定义,你很快就会发现这个词是由两个词组成的复合词: *hyper* 和 *visor* 。可以追溯起源到希腊语单词 *hyper* ,意为上方,而 *visor* 一词来源于古英法单词 *visor* ,意为隐藏。[维基百科。com](http://wikipedia.com) 暗示 *hypervisor* 是单词 *supervisor* 的变体。根据维基百科, *hypervisor* 这个词是 supervisors 的 supervisor。单词 *hyper* 比单词 *super* 有更强烈的同义词含义,至少在语言学上是这样。 如果你也对 DC 漫画超级英雄角色感兴趣,你知道最强大的角色是超人(虽然很多人会在这里争论)。但如果通过 DC 漫画创作出一个超男角色并使其成名,那么这个超男甚至会比超人还要强大。 为了更现实地理解虚拟化的概念,可以将虚拟机管理程序想象成汽车引擎中使用的润滑剂。典型的内燃机汽车有较大的部件,如发动机和变速器。在一个较大的组件内部,有许多较小的移动部件。将虚拟机管理程序视为汽车引擎的润滑剂,以防止位于较大部件内部的几个较小部件发生摩擦和磨损。虚拟机可以在其托管服务器上平稳运行,很少或没有兼容性问题。虚拟机管理程序对虚拟机隐藏(或伪造)了真实的硬件,认为它是一台真实的计算机,但虚拟机是运行在虚拟机管理程序上的一些文件。在这个意义上,虚拟机监控程序与单词 *fabrication* 相关联,单词 *fabrication* 的意思是假的。虚拟机认为它们运行在真正的基于硬件的服务器上,但所有硬件都是文件,因此虚拟机是运行在软件上的一堆文件。 我支持的一些客户环境仍然使用 Cisco 6500/4500 系列企业交换机和 Cisco 9000 系列交换机,它们的大型框架交换机主体中插入了管理模块。如果你正在研究 CCNA,你可能听说过这些企业级交换机。根据打开的操作系统和功能集,这些交换机可以成为路由器、交换机或两者。一些老工程师称这些设备为路由器,因为它们可以支持多个 OSI 层。这些设备中的管理程序是虚拟化技术的模块化硬件版本,作为具有 CPU 和 ram 的独立操作系统运行。甚至在虚拟化技术几年前起飞之前,思科就已经开始使用各种模块进行基于硬件的虚拟化。作为参考,当前网络市场上提供的 Nexus 3K/ 5K/6K/7K 和企业 9K 交换机支持 Python APIs,默认情况下附带 Python 2.7.2 或更新版本。然而,像许多思科产品一样,内置于这些交换机中的 Python 版本有一些限制,并且仅限于您可以使用它做什么。有关这些交换机的更多信息,请访问 [Cisco。com](http://cisco.com) ( [` www .思科。com/c/en/us/products/switches/catalyst-9400 系列-switches/ index。html`](https://www.cisco.com/c/en/us/products/switches/catalyst-9400-series-switches/index.html) 。 # 七、Linux 基础 在本章中,您将学习 Linux 基础知识,以便快速掌握在第八章中学习的基本 Linux 管理技能。因为您可能已经从事网络自动化工作,所以您不必成为全职的 Linux 管理员。然而,您必须知道您在这些系统上正在做什么,这样您就不会停止运行在 enterprise Linux 系统上的关键服务。其他 Python 网络自动化书籍经常忘记提及一般 Linux 管理技能对于网络自动化的重要性。事实上,Linux 和 Python 网络自动化是齐头并进的。 在这一章中,你将学习如何开始使用 Linux,以及关于目录和文件结构。然后您将学习使用 vi 和 nano 进行基本的文件和目录管理。本章结束时,你将能够使用 Linux 终端和文本编辑器来管理目录和文件。 ![img/492721_1_En_7_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_7_Figa_HTML.jpg)本章没有安装前任务,但本章假设您已经阅读并完成了第 1–3 章节。您将使用之前章节中安装的 Ubuntu Server 20 LTS 和 CentOS 8.1 虚拟服务器。如果您尚未完成这些安装,请返回到前面的章节并创建您的虚拟机。您可以从以下 GitHub 网站下载其他安装指南: URL: [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) 在本章中,您将使用 CentOS 8.1 和 Ubuntu Server 20 LTS 虚拟机学习 Linux 基础知识。图 7-1 显示了本章所需的逻辑实验拓扑。您需要启动并运行两个 Linux 服务器,通过 VMnet8 (NAT)网络连接,并且您的 Windows 主机连接到互联网。VMnet8 网络子网的第三个二进制八位数可能与图中所示的不同。如果是这样,您将在给定的子网内工作,或者您可以从 VMware Workstation 的虚拟网络编辑器中更新 VMnet8 子网,使该子网也位于 192.168.183.0/24 子网中。 ![img/492721_1_En_7_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_7_Fig1_HTML.jpg) 图 7-1。 逻辑网络拓扑 ## 为什么要学 Linux? 本章致力于学习 Linux 基础知识。您将了解 Linux 的历史、不同类型的 Linux 发行版、基本的文件和目录管理,以及如何使用 vi/nano 文本编辑器进行基本的 Linux 管理。您可能是一个内容 Windows 用户,或者问“为什么 IT 技术人员应该学习 Linux?”或者“为什么有人要学 Linux?”我对这个问题的简单回答是,作为一个 IT 技术人员学习 Linux 没有什么损失;这包括网络、安全、系统和软件工程师等。目前的 IT 行业缺乏拥有良好 Linux 管理技能的 IT 工程师,这表明如果你既懂 Linux 又懂 Windows 管理,那么会有很多工作机会。 直截了当地说,从微软 Windows 用户的角度来看,试图驯服和掌握 Linux 操作系统(OS)并平稳过渡到 Linux 是一条崎岖的道路。对许多人来说,失去 Windows 或图形用户界面(GUI)是一件大事。如果你已经精通 Linux,那就太棒了;你领先了一步,而且是当之无愧的领先。大约 20 年前,当我得到我的第一份 IT 支持工作时,我遇到了一位导师,他建议我对 Linux 感兴趣,然后从事 Linux 管理员的职业。当时,我对思科网络、统一通信和 Windows 更感兴趣,所以我没有找到一个令人信服的理由认真学习 Linux。直到 2015 年,我才开始对 Python 编码产生兴趣。我意识到我有多后悔没有接受以前导师的建议去学习 Linux。在我们的行业中,很多 IT 工程师,尤其是网络工程师,都会犯同样的错误。我现在和其他 Windows 用户分享我的故事,这样他们就能对 Linux 保持开放的心态。不要错过这个机会。 至少在企业级,很大一部分应用服务器运行在某种基于 Linux 的操作系统上。它可能是亚马逊网络服务(AWS)或云中的脸书或谷歌;应用服务器需要一个安全可靠的操作系统来日复一日地运行。在过去的 15 年中,Linux 服务器的使用量与云的使用量同步大幅增长,大数据业务也获得了发展势头。多年来,在企业 IT 和数据中心环境中,越来越多的企业系统从 Windows 迁移到 Linux。这一趋势背后有许多原因,但仅举几个例子,Linux 提供了更高的性价比;它具有更好的可靠性、安全性、可扩展性、高可用性和灵活性,这是 Windows server 环境所不具备的。Linux 现在是我们生活方式的新标准。2020 年,微软发布了 Windows 10 补丁,内置了 Linux 内核支持,支持 Windows Subsystem for Linux (WSL)。所以,现在你甚至可以在 Windows 上运行 Linux 虚拟机。 ![img/492721_1_En_7_Figb_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_7_Figb_HTML.gif)访问 Microsoft Windows 文档网站,阅读有关 WSL 的更多信息。 URL: [`https://docs.microsoft.com/en-us/windows/wsl/install-win10`](https://docs.microsoft.com/en-us/windows/wsl/install-win10) 如果您已经在企业网络行业工作了一段时间,那么您将对思科技术最为熟悉。仅以思科为例,大约 20 年前,思科的大部分网络、安全和语音服务应用都基于 Windows 2000 和 2003。一个很好的例子是 Cisco 的 IP PABX 服务器,称为 Cisco CallManager(版本 3 和 4.1),它是建立在 Windows 2000 服务器上的。其他例子包括旧的 UCCX、UCCE 和 CiscoWorks 服务器;他们都使用 Windows 服务器作为基础平台。然而,在 2010 年,思科决定放弃 Windows 服务器,转而采用设备模式;它将其许多应用服务器迁移到基于 Red Hat 的 Linux 操作系统(OS)上。甚至在 2010 年之前,思科放弃 Windows 服务器并用 Linux 取而代之的决定就符合 IT 趋势。此外,在 Juniper 的核心操作系统中可以找到 Linux 操作系统的改编版本,如 Junos 和 Palo Alto 的 PAN 产品;它们都使用 Linux 作为基本操作系统。 如前所述,Linux 相对于 Windows 的一些优势包括稳定性、高安全性和较低的拥有成本。Linux 是一个有效的企业操作系统,所有 IT 工程师都必须学习并应用到他们的工作中。并不是所有人都真的想以 Linux 系统管理员为生,但是我们必须知道足够多的知识来保住我们作为 IT 技术人员的工作。在本书中,您希望学习足够的 Linux 基础知识,以便能够轻松地执行基本的实验任务。如果你打算专业学习 Linux 成为一名 Linux 系统管理员,可以系统学习红帽 8 认证。在本书中,我们将学习精选的 Linux 资料,以便开始学习 Python 网络自动化。 要了解一项技术,从一点历史或者它的起源开始总是好的。让我们从一些 Linux 历史开始,了解我们可用的不同类型的 Linux 发行版,研究 Linux 文件和目录结构,并掌握 vi 和 nano 文本编辑器,以便在没有图形用户界面(GUI)帮助的情况下完成工作。但是万一你因为某种原因需要一个 GUI,我们已经在我们的服务器上有了可选的基于 GUI 的 Linux 桌面。在本章的最后,你需要在 Ubuntu 和 CentOS Linux 服务器上执行一些基本的管理。 ## Linux 的开始 为了理解 Linux,让我们快速回顾一下 Linux 的早期历史,然后回顾一下当今最流行的 Linux 发行版。供您参考,表 7-1 中的时间表只显示了直到 1991 年第一个 Linux 发行版发布之前的最初开发亮点。如果您想找到其他时间线,请参考 [`https://en.wikipedia.org/wiki/Linux`](https://en.wikipedia.org/wiki/Linux) 。 表 7-1。 早期 Linux 开发时间表 | 年 | Linux 开发亮点 | | --- | --- | | One thousand nine hundred and sixty-nine | 与 Ken Thompson、Dennis Ritchie、Douglas Mcilroy 和 Joe Ossanna 在美国电话电报公司实验室首先开发了 UNIX。 | | One thousand nine hundred and seventy-one | Unix 最初是用汇编语言编写的。 | | One thousand nine hundred and seventy-three | Dennis Richie 首先使用 C 语言编写了 UNIX。 | | One thousand nine hundred and eighty-three | 理查德·斯托尔曼引入了 GNU 项目。 | | One thousand nine hundred and eighty-four | 完整的 UNIX 兼容软件系统。自由软件开发开始了。 | | One thousand nine hundred and eighty-five | 理查德·斯托尔曼建立自由软件基金会。 | | One thousand nine hundred and eighty-nine | 引入了通用公共许可证(GNU)。 | | One thousand nine hundred and ninety | 许多程序是为操作系统开发的。 | | 1991 年 8 月 25 日 | 当时 21 岁的芬兰学生林纳斯·本纳第克特·托瓦兹公开宣布 Linux 内核的免费版本。1991 年的 Linux 内核是在 GNU GPL 许可下发布的。 | 来源: [`https://en.wikipedia.org/wiki/Linux`](https://en.wikipedia.org/wiki/Linux) 从表 7-1 可以看出,Linux 植根于 UNIX。我们可以猜测,Linux 的语法和操作将与 UNIX 相似。自从 Linus Torvalds 在 1991 年发布免费的 Linux 内核以来,许多 Linux 发行版都是在 GNU 下开发和发布的。有些 Linux 类型是商业驱动的,有些是为社区或个人使用而创建的。大多数 Linux 发行版可以运行商业应用,但也有一些提供了很好的桌面版本,如 Ubuntu desktop。 表 7-2 列出了 2017 年以来最流行的十个 Linux 发行版,以及每个 Linux 类型的一些特征。如果您的企业基础设施需要 Linux 操作系统,最推荐的 Linux 系统是 Red Hat Enterprise Server。如果你必须用它来工作和学习,Ubuntu、CentOS 和 Debian 是非常灵活和用户友好的 Linux 发行版。在这三个非常流行的 Linux 发行版中,Ubuntu 具有最用户友好的特性,如果需要的话,它有一个很棒的 GUI,是初学者的绝佳选择。注意 Ubuntu 是 Debian 的衍生品。CentOS 使用与 Red Hat 和 Fedora 相同的 Linux 内核和软件。这里提到的三个 Linux,Red Hat、CentOS 和 Fedora,都是相同的 Linux 发行版,只是在美学上稍有不同。Fedora 是 Red Hat 的开发版本。红帽是面向大型企业的企业级商业化 Linux,CentOS 是红帽的开源版本。Fedora 提供最新的测试版软件,但缺乏 CentOS 和 Red Hat Linux 的稳定性。因此,在学习 Linux 时,任何版本的 CentOS server 或 CentOS Stream 都应该是 Red Hat 衍生的 Linux 版本的首选 Linux 发行版。Linux 开源版本和商业版本之间的主要区别在于,您是从供应商支持团队获得全面的技术支持,还是从 Linux 开发人员社区获得尽最大努力的支持。现在快速查看表 7-2 并找到您想要使用的 Linux 发行版。大多数 Linux 软件对个人使用是免费的,没有商业限制。 表 7-2。 2017 年十大 Linux 服务器发行版 | 军阶 | Linux 在这里 | OS 衍生物 | 用户技能水平 | 优势 | | --- | --- | --- | --- | --- | | one | Ubuntu 服务器 | 基于 Debian 的 | 中间的 | 性能、稳定性、灵活性、DC 支持、安全 | | Two | 红帽企业 Linux (RHEL) | 基于 Fedora 的 | 先进的 | 供应商支持、性能、安全、云和物联网支持、数据和虚拟化支持 | | three | 企业服务器 | 基于 RPM | 中间的 | 开源、稳定、安全的云服务支持 | | four | CentOS | 总部设在 RHEL | 中间的 | 免费社区支持;与没有标志的 RHEL 相同 | | five | 一种自由操作系统 | 基于 Debian 的 | 中级到高级 | 免费,开源,稳定,教育,商业,政府和非政府组织可以使用 | | six | Oracle Linux(Oracle 企业 Linux) | 总部设在 RHEL | 先进的 | 免费、开源、云外、中小型企业和企业使用、云和数据中心支持 | | seven | Mageia | 基于 Mandriva 的 | 中间的 | 免费、稳定、安全、社区支持、一系列软件、MariaDB 支持 | | eight | 克利罗斯 | 总部设在 RHEL/CentOS | 中级到高级 | 开源、SMB 支持、商业用途、网络网关和服务器支持、web 服务器支持 | | nine | Arch Linux | 其他的 | 先进的 | 开源、简单、性能优化、稳定、灵活 | | Ten | slackware | 基于 Slackware | 先进的 | 免费,开源,最接近的 UNIX 系统,简单,稳定 | 来源: [`https://www.tecmint.com/10-best-linux-server-distributions/`](https://www.tecmint.com/10-best-linux-server-distributions/) ## 了解 Linux 环境 Linux 是安装在物理或虚拟机上用于个人或商业用途的操作系统(OS)。Linux 桌面和 Linux 服务器的显著区别是图形用户界面。Linux 服务器通常被安装来为用户或企业提供特定的服务,并且通常由精通 Linux 命令的 Linux 管理员来管理。为了获得更好的性能、稳定性和更高的安全性,Linux 通常会安装最低版本的软件。除此之外,Linux 桌面和服务器版本对 Linux 操作系统的每个发布版本使用相同的基本内核。如前几章所示,您可以选择在 Linux 服务器上安装 GUI。在本书的前面,为了学习和方便,您在 Linux 服务器上安装了一个桌面。随着您对 Linux 及其命令行的熟悉,您将慢慢脱离 GUI,能够仅使用命令行运行所有基本的 Linux 管理任务。你会发现,在 Linux 中,一切都是由文件和目录组成的;这是一个免费且非常灵活的操作系统。同样,如果您习惯于通过命令行界面(CLI)来管理 Cisco 设备,那么您会有宾至如归的感觉。即使你没有思科网络背景或零 Linux 经验,你也能跟上;我们将只讨论 Linux 的基础知识,这样您就可以运行实验来结束您的第一次 Python 网络自动化之旅。 ## 了解 Linux 目录和文件格式 前面,我们简要地看了一下 Linux 开发时间表,了解了流行的 Linux 发行版类型。接下来,让我们快速看一下 Linux 目录结构。在 Linux 中,所有的东西都是用文本写的,保存在不同的目录中。由于 Windows 操作系统也可以追溯到*nix,所以 Windows 文件和文件夹结构是相似的。但是 Linux 不像 Windows 一样有`C:`驱动;相反,它有根文件夹,或者说`/.`图 7-2 描述了一个通用的 Linux 目录结构以及每个目录中通常存放的文件类型。当然,您可以像在 Windows 操作系统中一样创建自定义目录和文件。如果您忘记了每个目录的用途,请查看该图并将其作为参考点。这个阶段不用全部背下来,但是看一看,看能不能发现什么有趣的东西。 ![img/492721_1_En_7_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_7_Fig2_HTML.jpg) 图 7-2。 典型的 Linux 目录和文件结构 ![img/492721_1_En_7_Figc_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_7_Figc_HTML.gif)图 7-2 显示了一个典型的 Linux 目录结构。不同的 Linux 类型会有稍微不同的目录和文件结构,但是通常彼此是相同的。 图 7-3 显示了 CentOS 8 服务器的根目录。仔细查看并比较列出的目录;你会看到与图 7-2 的相似之处。 ![img/492721_1_En_7_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_7_Fig3_HTML.jpg) 图 7-3。 CentOS 8 Linux 目录示例 ## 虚拟化与纳米化 您已经简要了解了典型的 Linux 目录和文件结构。现在让我们考虑一下 Linux 文件和目录管理。已经具备良好 Linux 知识的读者会问:“为什么在这里教如何使用 vi 文本编辑器?”如果你是一个经验丰富的 Linux 用户,你可以跳过这一章的大部分内容,但是在继续阅读第九章之前,请务必阅读第八章,以便在 CentOS 8.1 服务器上安装各种 ip 服务。如果你对 Linux 接触不多,在跳到下一章之前,一定要学习 vi(可视化编辑器)和 nano。您需要熟悉 Linux 文本编辑器,并通过终端控制台或命令行执行大多数任务。在 Linux 服务器上,几乎所有的东西都是由文本文件组成的,您应该使用终端控制台来管理您的日常任务。换句话说,您必须通过命令行熟悉 Linux 文件结构、文件和目录管理。通常,当你在 Linux 服务器上执行一个任务时,你的意图必须是明确的,你确切地知道你想从你的行动中达到什么目的。学习使用 Linux 的文本编辑器对于学习 Python 来说是必不可少的,因为基于脚本的编码需要在您最喜欢的文本编辑器中花费很多时间。 当然,Linux 有各种各样的文本编辑器,但是大多数现代 Linux 系统上的标准文本编辑器是 vi。要想精通 Linux,学习如何使用它不是可有可无的,而是必须的。是的,vi 在黑色背景上缺少用户控件,对于第一次使用 Linux 的用户来说可能有些笨拙。让我们一劳永逸地学习如何使用 vi 编辑器,这样你就不必担心再次学习这个可怕的文本编辑器(无论如何,最初是可怕的)。 在本章中,我们鼓励您使用 vi 和一个名为 nano 的辅助文本编辑器来探索文件和目录管理。这是另一个最适合初学者的 Linux 文本编辑器,因为它使用伪图形布局。与 vi 不同,它不是预装在所有的 Linux 发行版上,但是了解这两者是有好处的。如上所述,学习如何使用 vi 不是可选的。在客户的生产环境中管理企业服务器和网络设备时,它可能是唯一可用的文本。 首先,让我们学习如何使用 vi 文本编辑器,它将成为您选择的主要 Linux 文本编辑器,然后您将学习如何使用 nano 文本编辑器作为您选择的第二个文本编辑器。 ### vi 简介 我们可以说,Linux 中的 vi 源于 UNIX 中的 vi。Linux 中的 vi 与 UNIX 操作系统中的 vi 几乎相同,因此很清楚 Linux OS 及其应用的起源。首先,vi(“可视化编辑器”)通过将命令和插入模式分开,使用 to 模式来防止无意的编辑。在 vi 中有两种操作模式。 * *命令模式*:这是第一次打开文件的起点。在这种模式下,输入的每个字符都是一个命令,用来改变正在打开的文本文件。 * *插入模式*:这是编辑模式。在这种模式下,键入的每个字符都被添加到文件的文本中;按 Esc 键关闭插入模式并返回命令模式。 第二,在命令模式下,vi 有许多有用的命令,但是您只需要几个命令就可以开始了。表 7-3 包含基本但最有用的 vi 命令。经过一些练习,你的手指会比你的大脑先记住这些命令。 表 7-3。 vi 文本编辑器基本命令 | Vi 命令 | 描述 | | --- | --- | | 基本的 | `vi` | 打开 vi 文本编辑器 | | `i` | 进入插入模式 | | 转义字符 | 退出插入模式 | | `:x` | 退出 vi 而不保存更改 | | `:q`或`:q!` | 退出或强行退出 | | `:w` | 写 | | `:wq`或`:wq!` | 保存并退出 | | 移动 | `j` | 将光标下移一行 | | 8 键 | | $ key | | `k` | 将光标上移一行 | | #键 | | `h` | 将光标向左移动一个字符 | | 退格键 | | !键 | | `l`(小写 L) | 将光标向右移动一个字符 | | 空格键 | | “钥匙 | | 0(零) | 将光标移动到当前行的开头(光标行) | | `$` | 将光标移动到当前行的末尾 | | `w` | 将光标移动到下一个单词的开头 | | `b` | 将光标移回到前一个单词的开头 | | `:0` | 将光标移动到文件中的第一行 | | `:n` | 将光标移动到第 n 行 | | `:$` | 将光标移动到文件的最后一行 | | 复制/粘贴 | `dd` | 复制(剪切) | | `p` | 在光标下粘贴 | | `P` | 粘贴到光标上方 | | 取消 | `u` | 撤消最后的更改,一个简单的切换 | | 搜索文本 | `/string` | 向前搜索文本中出现的字符串 | | `?string` | 向后搜索文本中出现的字符串 | | `n` | 移动到搜索字符串的下一个匹配项 | | `N` | 以相反方向移动到搜索字符串的下一个匹配项 | 如果你已经浏览了表 7-3 大约五次,并且记住了 vi 的一些关键命令,现在你可以在 Ubuntu 或 CentOS 服务器中打开 VMware Workstation 和 SSH,为本章的第一个练习做准备(图 7-4 )。在真实的生产环境中,系统管理员通常使用 Putty、Tera Term 或 SecureCRT 等 SSH 客户端远程连接到 Linux 服务器和网络设备。您更喜欢使用哪个 Linux 服务器并不重要。尽管我们是在一台计算机上工作,但是请把您的服务器想象成一个远程 Linux 服务器,把您的主机的 Windows 操作系统想象成远程客户机。让我们练习一些基本的 vi 和 Linux 命令。 ![img/492721_1_En_7_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_7_Fig4_HTML.jpg) 图 7-4。 使用 PuTTY 通过 SSH 登录 CentOS 8.1 #### 虚拟仪器基础 对一些人来说,学习如何使用 vi 可能是 Linux 的第一个障碍,但对其他人来说,vi 只是他们必须获得的另一个工具,这样他们就可以自豪地把它挂在腰带上。随着实践的进行,您将熟悉 vi,并且离开发良好的 Linux 管理技能更近了一步。 在输入每个 Linux 命令后,你必须按下键盘上的回车键。在练习的某些部分,您必须按回车键,您可能会看到左箭头(↲)符号。 这里有一个例子:`:wq` (↲). 前面的例子意味着在你输入`:wq`后,你必须按回车键。 在下面的练习中,用户使用用户名`pynetauto`通过 SSH 连接登录 CentOS 8.1 服务器。 1. 查看当前工作目录(`pwd`),制作一个名为`ex_vi`的工作目录(`mkdir`,列出当前工作文件夹中的(`ls`)文件和目录,将目录(`cd`)更改为`ex_vi`目录。最后,再次检查当前工作目录(`pwd`)。 目标:学习如何检查、创建和导航到一个新目录。 `---------------------------------------------------------------------` `[pynetauto@localhost ~]$ pwd` `/home/pynetauto` `[pynetauto@ localhost ~]$ ls` `Desktop Documents Downloads Music Pictures Public Templates Videos` `[pynetauto@ localhost ~]$ mkdir ex_vi` `[pynetauto@ localhost ~]$ ls` `Desktop  Downloads Music   Public   Videos` `Documents ex_vi   Pictures Templates` `[pynetauto@ localhost ~]$ cd ex_vi` `[pynetauto@ localhost ex_vi]$ pwd` `/home/pynetauto/` `ex_vi` `---------------------------------------------------------------------` 2. 当文本文件在 vi 中打开时,您会注意到这是一个合适的极简文本编辑器。vi 最令人兴奋的地方是显示在应用底部的关于文件名和行号的信息。试着在键盘上敲点什么;您不能在命令模式下键入任何内容。转到下一个练习,将数据(文本)添加到该文件中。 您可以通过键入`vi filename`然后按回车键来创建一个新文件。 目标:学习如何创建/打开文件。 `---------------------------------------------------------------------` `[pynetauto@localhost ex_vi]$ vi myfile01.txt` `---------------------------------------------------------------------` 当新文件打开时,您会看到空行,每行都有一个波浪号(`~`),并且在底部有一行显示新文件的文件名和状态。 `---------------------------------------------------------------------` `~` `~` `~` `"myfile01.txt" [New File]             0,0-1     All` `---------------------------------------------------------------------` 3. 如果要在文本文件中输入文本,必须首先进入插入模式。首先,按下键盘上的 I 键进入`-- INSERT --`模式。如果您在左下角看到`--INSERT--`,那么 vi 现在已经准备好接受您的输入了。 目标:学习如何在 vi 中进入插入模式。 按下键盘上的 I 键。 `---------------------------------------------------------------------` `~` `~` `~` `-- INSERT --                    0,1      All` `---------------------------------------------------------------------` 4. 现在,在这个文件中键入一些文本,然后按键盘上的 Esc 键退出插入模式并返回到命令模式。一按 Esc 键,就看左下角。`-- INSERT --`从屏幕上消失,你处于 Ex 模式。现在输入`:wq`或`:wq!`并按下输入↲键保存更改并退出 vi。 目标:学习如何在 vi 中通过按`:wq` ↲来编写、保存和退出文件 `Python lives in the jungle.` `Also, python lives on my computer` `.` `Have a Pythonic day!` `~` `:wq`ⅱ 5. 和前面的例子一样,在对文件进行修改后,您通常希望在退出 vi 时保存修改后的文件。另一种退出 vi 的方法是在命令模式下键入`:x`。 目标:学习在 vi 中保存和退出文件的另一种方法。 `Python lives in the jungle.` `Also, python lives on my computer.` `Have a Pythonic day!` `~` `:x`ⅱ 6. 一旦退出 vi 并回到 Linux 终端控制台,发出`ls` ( `list`)然后发出`cat myfile01.txt`来验证更改。 目标:学习在 vi 中列出当前目录下的(`ls`)文件,查看文件内容。 `[pynetauto@localhost ex_vi]$ ls` `myfile01.txt` `[pynetauto@localhost ex_vi]$ cat myfile01.txt` `Python lives in the jungle.` `Also, python lives on my computer` `.` `Have a Pythonic day!` 7. 现在,这里将向您介绍一些新的 Linux 命令来执行以下任务。您将学习如何作为一个`sudo`用户登录,检查当前的工作目录,找到您的文本文件,将文件复制到根目录,然后返回到普通用户模式。你已经看到了本章接下来的内容。 目标:学习以 root 用户身份登录,检查文件是否存在,搜索文件,并将文件复制到 root 用户文件夹中。 `[pynetauto@localhost ex_vi]$ pwd` `/home/pynetauto/ex_vi` `[pynetauto@localhost ex_vi]$ su -` `Password: **********` `[root@localhost ~]# pwd` `/root` `[root@localhost ~]# ls myfile*` `ls: cannot access 'myfile*': No such file or directory` `[root@localhost ~]# find / -name myfile*` `/home/pynetauto/ex_vi/myfile01.txt` `[root@localhost ~]# cp /home/pynetauto/ex_vi/myfile01.txt ~/` `[root@localhost ~]# ls myfile*` `myfile01.txt` `[root@localhost ~]# find / -name myfile*` `/root/myfile01.txt` `/home/pynetauto/ex_vi/myfile01.txt` #### 在 vi 中重新打开文件 在这里,您将练习在 vi 中重新打开和关闭文件。 1. 如果您继续上一个练习,您应该仍然处于 root 用户模式。现在让我们使用`su - username`回到用户模式。检查当前工作目录,然后移动到原来的`ex_vi`目录。 用您自己的用户名替换用户名。现在你应该回到用户的主目录和`ex_vi`目录。 目标:学习作为标准用户重新登录。 `[root@localhost ~]# su - pynetauto` `[pynetauto@localhost ~]$ pwd` `/home/pynetauto` `[pynetauto@localhost ~]$ ls` `Desktop  Downloads Music   Public   Videos` `Documents ex_vi   Pictures Templates` `[pynetauto@localhost ~]$ cd ex_vi` `[pynetauto@localhost ex_vi]$ pwd` `/home/pynetauto/ex_vi` 2. 要重新打开一个现有的文件,只需键入`vi`,后跟文件名。如果你不小心打开了一个文件,不想做任何修改,只需输入`:q`并按回车键。 目标:学习从 vi 中重新打开现有文件并退出(退出)而不修改文件。 `[pynetauto@localhost ex_vi]$ ls` `myfile01.txt` `[pynetauto@localhost ex_vi]$ vi myfile01.txt` `Python lives in the jungle.` `Also, python lives on my computer.` `Have a Pythonic day!` `~` `:q ↲` 3. 如果您打开文件,进入插入模式,并开始编辑文件,但决定不保存更改就退出,您可以使用`:q!`。在这个例子中,`“Goodbye.”`被添加到第四行,但是我们想放弃它并退出而不保存更改。 目标:修改文件后学会退出。 `Python lives in the jungle.` `Also, python lives on my computer.` `Have a Pythonic day!` `Goodbye` `.` `~` `:q! ↲` 如果你修改了文件,只使用了`:q`并按下回车键,vi 会要求你在`:q`后添加`!`。 `Python lives in the jungle.` `Also, python lives on my computer.` `Have a Pythonic day!` `Goodbye.` `~` `E37: No write since last change (add ! to override)                                     4,8     All` 4. 要查看文件的内容而不在 vi 中打开它,您可以使用`cat`、`more`和`less`命令。尝试所有三个命令,但是当您使用`less`命令时,您可以使用 Ctrl+Z 键退出。 目标:学习使用`cat`、`more`和`less`命令查看文件内容。 `[pynetauto@localhost ex_vi]$ cat myfile01.txt` `Python lives in the jungle.` `Also, python lives on my computer.` `Have a Pythonic day!` `[pynetauto@localhost ex_vi]$ more myfile01.txt` `Python lives in the jungle.` `Also, python lives on my computer` `.` `Have a Pythonic day!` `[pynetauto@localhost ex_vi]$ less myfile01.txt` `[2]+ Stopped         less myfile01.txt` #### 在 vi 中复制和粘贴一行 您可以使用`dd`在 vi 中复制一行,并使用 P 键在光标位置下方粘贴一行。或者使用 P 键在光标位置上方粘贴一条线。你可以很容易地记住这个操作。尝试使用 P 键粘贴到光标位置下方,使用 P 键粘贴到光标位置上方。 1. 再次打开 vi 中的`myfile01.txt`。将光标移动到第三行,因为您要剪切第三行。 目的:在 vi 命令模式下移动光标。 `Python lives in the jungle.` `Also, python lives on my computer.` `|Have a Pythonic day!                       # cursor line` `~` `"myfile01.txt" 3L, 84C                    3,1      All` 2. 连续按两次 D 键。该操作将剪切光标所在的行。 目标:使用`dd.`切断活动线上的字符串 `dd` `Python lives in the jungle.` `|Also, python lives on my computer.` `# cursor line` `~` `~` `"myfile01.txt" 3L, 84C                    3,1      All` 3. 现在按 P 键粘贴“有一个 Pythonic 式的一天!”之前“还有,python 在我的电脑上。”您已经成功交换了第二行和第三行。 目标:将复制的行粘贴到光标位置上方的行。 `P` `Python lives in the jungle.` `|Have a Pythonic day!                       # cursor line` `Also, python lives on my computer.` `~` `"myfile01.txt" 3L, 84C                    2,1      All` 4. 现在你想在第四行加上同样的句子。将光标移动到第三行位置。 目标:向下移动光标。 `Python lives in the jungle.` `Have a Pythonic day!` `# cursor line` `|Also, python lives on my computer.` `~` `"myfile01.txt" 3L, 84C                    3,1      All` 5. 这次按 P 键粘贴“这是我的文件 01。”第一行以上。 目标:将复制的行粘贴到光标位置下方的行。 `p` `Python lives in the jungle.` `Have a Pythonic day!` `Also, python lives on my computer.` `|Have a Pythonic day!` `# cursor line` `~` `4,1      All` 6. 现在按一次 Esc 键,然后使用`:wq`保存更改并退出 vi。 目标:保存更改并退出 vi。 `:wq ↲` `Python lives in the jungle.` `Have a Pythonic day!` `Also, python lives on my computer.` `Have a Pythonic day!` `~` `:wq ↲` #### 撤消 vi 中的最后一个 如果出错,您可以在命令模式下使用`:u`撤销上一次操作。 1. `:u`(撤销)是基本命令之一,因为它允许你撤销你的最后一个操作。在 vi 中,这就像一个开关,撤销和重做你最近的动作。首先,在`–INSERT--`模式下,重新打开`myfile01.txt`,在第 5 行输入一些内容,如下所示。 目标:学习撤销 vi 中的最后一个动作。 `Python lives in the jungle.` `Have a Pythonic day!` `Also, python lives on my computer.` `Have a Pythonic day!` `You can do it!|` `# cursor line` `~` `-- INSERT --                        All` 按 Esc 键退出插入模式并返回命令模式。键入`:u`并按下键盘上的 Enter 键来撤销您的上一个操作。 `Python lives in the jungle.` `Have a Pythonic day!` `Also, python lives on my computer` `.` `Have a Pythonic day!` `You can do it!` `~` :u ↲ 一旦你按下回车键,你刚刚添加的第五行将被删除(撤消)。 `Python lives in the jungle.` `Have a Pythonic day!` `Also, python lives on my computer.` `Have a Pythonic day!` `~` `1 line less; before #1 23:18:48        4,20     All` 有些人非常喜欢 vi 编辑器,他们会在 Windows 上安装一个名为 gvim 的 Windows 版本的 vi,并将其用作他们首选的 Windows 文本编辑器。对于大多数新用户来说,你可能需要一段时间来适应 vi。 URL: [` www。我来了. org/ download. php`](https://www.vim.org/download.php) #### 在 vi 中搜索文本 vi 编辑器还提供了字符串搜索。您可以在命令模式下使用`/string`来搜索您正在寻找的字符串。Cisco IOS `show`命令也提供此功能。例如,在您的 Cisco 路由器上运行`show run`命令时,您可以使用`/line vty`跳到线路的末端。 1. 再次,在 vi 中打开`myfile01.txt`文件。您可以使用字符串来搜索文本。将光标放在文件的开头,并在命令模式下键入`/Python`。搜索词将会突出显示。记住 Python 中的 P 是大写字母,所以这个搜索区分大小写。请注意,第三行的单词`python`没有突出显示。 目标:学习在 vi 中搜索字符串和浏览文本文件。 `Python lives in the jungle.` `Have a Pythonic day!` `Also, python lives on my computer.` `Have a Pythonic day!` `~` `/Python` `# cursor line` 按一次回车键,移动到第一个出现的字符串`Python`。光标移动到搜索单词的第一个字母。 再输入一次`/Python`,然后按回车键找到下一个出现的字符串。 `Python lives in the jungle.` `Have a Pythonic day!` `Also, python lives on my computer.` `Have a Pythonic day!` `~` `/Python ↲` 按回车键,然后按一次 N 键,移动到最后出现的字符串`Python.` `Python lives in the jungle.` `Have a Pythonic day!` `Also, python lives on my computer.` `Have a |Pythonic day!` `# cursor line` `~` `/Python              4,8     All` 按 Shift+N 向后移动。注意左下角的`/`符号变成了`?`。 `Python lives in the jungle.` `Have a |Pythonic day!` `# cursor line` `Also, python lives on my computer` `.` `Have a Pythonic day!` `~` `?Python              2,8     All` ![img/492721_1_En_7_Figf_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_7_Figf_HTML.gif)如果想把 vim 做成 IDE,可以参考下面这篇文章做参考。 URL: [`https://realpython.com/vim-and-python-a-match-made-in-heaven/`](https://realpython.com/vim-and-python-a-match-made-in-heaven/) #### 在 vi 中显示行号 在 vi 文本编辑器中工作时,一些文本行或代码是可以管理的,但是随着文本/代码行的增加,您很容易迷失在文本/代码行中。为了跟踪当前的工作位置,vi 有一个编号功能。您可以通过在命令模式下键入`:set number`来打开行编号。 1. 在 vi 中重新打开`myfile01.txt`,输入`:set number`,按回车键。 目标:启用和禁用行号。 `1 Python lives in the jungle.` `2 Have a Pythonic day!` `3 Also, python lives on my computer.` `4 Have a Pythonic day!` `~` `:set number ↲` `4,1     All` **注意**如果你想关闭编号,在命令模式下键入`:set nonumber`。 `Python lives in the jungle` `.` `Have a Pythonic day!` `Also, python lives on my computer.` `Have a Pythonic day!` `~` `:set nononumber ↲` `4,1     All` vi 中有更多的命令,但是您已经完成的练习包含了开始使用 Linux 操作系统所需要的基本知识。接下来,您将了解 nano 文本编辑器。对于大多数 Windows PC 用户来说,在 Linux 操作系统环境下处理文件时,nano 可能是一个更好的文本编辑器。 ![img/492721_1_En_7_Figg_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_7_Figg_HTML.gif)如果您想了解更多关于 vi 的信息,请访问以下网址了解更多详情: URL: [`https://www.tecmint.com/vi-editor-usage/`](https://www.tecmint.com/vi-editor-usage/) URL: [`https://www.washington.edu/computing/unix/vi.html`](https://www.washington.edu/computing/unix/vi.html) ### 纳米简介 与 vi 不同,nano 在 Linux 上几乎没有学习曲线;这是一个易于使用的文本编辑器,具有通用和简单的用户界面。默认情况下,nano text editor 安装在大多数较新的 Linux 发行版上,但可能不会安装在较旧的发行版上。它还可以与`sudo`命令配合使用。在最新的 CentOS 8.1 上,应预装 nano 2.9.8 或更新版本,并可立即使用。如果您使用 Ubuntu Server 20 进行 Linux 练习,您应该已经预装了 nano 4.8 或更高版本。如果没有预装,那么只需要一个简单的`yum`命令就可以在您的 Linux 机器上安装这个程序。在安装 nano 文本编辑器之前,我们先来看看 nano 中使用的一些键盘命令。这些命令可从 nano 界面获得,因此您不必记住它们。查看表 7-4 中的可用命令,并在进入下一节之前至少阅读一次描述。 表 7-4。 nano 文本编辑器主键 | 命令 | 描述 | | --- | --- | | `nano[filename]` | 打开编辑器命令。 | | Ctrl+X | 退出编辑模式组合键。 | | Y | 按 Ctrl+X 并选择 Y 保存文件。 | | 普通 | 按 Ctrl+X 并选择 N 忽略编辑。 | | 取消 | 按 Ctrl+X 并选择取消返回到 nano 编辑器。 | | Ctrl+K | 复制(剪切)。 | | Ctrl+U | 浆糊。 | #### 安装 nano 让我们快速看看如何检查您的 Linux 操作系统是否已经预装了 nano。在下面的例子中,用户名`pynetauto`用于登录 Linux。如果您在 Linux 安装部分使用了另一个用户名,您的用户名将会不同。 1. 从您的 Linux 终端控制台,运行`nano –V`或`nano --version` `.`以下示例显示了 CentOS 8 上`nano –V`命令的输出。 目的:了解如何在 Linux 上检查 nano 版本或 nano 的可用性。 `nano –V or nano --version` `# On CentOS 8.1 server` `[pynetauto@localhost ~]$ nano -V` `GNU nano, version 2.9.8` `(C) 1999-2011, 2013-2018 Free Software Foundation, Inc.` `(C) 2014-2018 the contributors to nano` `Email: nano@nano-editor.org  Web:` `https://nano-editor.org/` `Compiled options: --enable-utf8` `# On Ubuntu 20.04 LTS server` `pynetauto@ubuntu20s1:~$ nano --version` `GNU nano, version 4.8` `(C) 1999-2011, 2013-2020 Free Software Foundation, Inc.` `(C) 2014-2020 the contributors to nano` `Email: nano@nano-editor.org  Web:` `https://nano-editor.org/` `Compiled options: --disable-libmagic --enable-utf8` 2. 检查 Linux 系统上是否安装了 nano 的另一种方法是使用`nano [filename]`命令创建并打开一个新文件。如果您的 Linux 系统上没有安装 nano text editor,Linux 将返回一个`“No such file or directory” –bash`错误。此外,当我们之前研究 Linux 目录结构时,我们看到可执行程序文件通常驻留在`/usr/bin/;`中,快速检查该目录下是否存在 nano 以再次确认。 目标:通过查看`/usr/bin`目录,检查您的 Linux 系统上是否有 nano。 `[pynetauto@localhost ~]$ nano -V` `bash: nano: command not found...` `Install package 'nano' to provide command 'nano'? [N/y] N` `[pynetauto@localhost ~]$ nano myfile02.txt` `bash: nano: command not found...` `Install package 'nano' to provide command 'nano'? [N/y] N` `[pynetauto@localhost ~]$ ls /usr/bin/nano` `ls: cannot access '/usr/bin/nano': No such file or directory` 3. 如果您遇到了与前面类似的错误,那么是时候在您的 Linux 操作系统上安装 nano 了。如果你像下面的例子一样以标准用户的身份登录,运行`sudo yum install –y nano`命令并输入`sudo`密码,你可以用一行带有`–y`句柄的代码来安装 nano。如果你作为根用户登录,你可以在`yum`命令前放下`sudo`。将`install`一词替换为`remove`卸载 nano。 目标:在您的 Linux 服务器上安装和卸载 nano。 `# To install nano on CentOS/Red Hat/Fedora Linux` `[pynetauto@localhost ~]$ sudo yum install nano` `[sudo] password for pynetauto: **********` `# To uninstall nano` `[pynetauto@localhost ~]$ sudo yum remove -y nano` `# To install nano on Ubuntu/Debian/Mint Linux` `pynetauto@ubuntu20s1:~$ sudo apt-get install -y nano` `[sudo] password for pynetauto: **********` `# To uninstall nano` `pynetauto@ubuntu20s1:~$ sudo apt-get remove -y nano` 如果你使用的是 Ubuntu Server 20 并且缺少 nano 编辑器,使用`apt-get install nano`命令安装 nano。CentOS 和 Ubuntu 之间细微的语法差异通常来自于不同安装程序的使用。在 CentOS、Red Hat 和 Fedora 等基于 Red Hat 的发行版上,您必须运行`yum`(*yellow dog Updater Modified*)命令。正在慢慢取代`yum`的新成员是`dnf`,它正在使用 RPM 包管理器。基于 Debian 的发行版如 Ubuntu、Debian 和 Mint Linux 使用`apt`(高级打包工具)作为安装程序。 4. 现在快速运行`ls /usr/bin/nano`命令来确认安装成功。 目标:检查 nano 安装是否成功。 `# On CentOS 8.1 server` `[pynetauto@localhost ~]$ ls /usr/bin/nano` `/usr/bin/nano` `# On Ubuntu 20.04 LTS server` `pynetauto@ubuntu20s1:~$ ls /usr/bin/nano` `/usr/bin/nano` #### 用 nano 创建/打开文件 您已经确认 nano 已成功安装,并准备好使用新应用编辑文件。如前所述,您可以使用您的 Ubuntu 或 CentOS 虚拟服务器进行此练习。 让我们以标准用户的身份重新登录,并创建一个新目录来开始 nano 练习。 1. 首先,通过键入`mkdir ex_nano.`创建一个名为`ex_nano`的新目录,您将使用该目录来存储在练习过程中创建的练习文件。通过键入`cd ex_nano`将工作目录更改为`ex_nano`。 目标:创建一个新的目录来练习使用 nano 文本编辑器。 `[pynetauto@localhost ~]$ mkdir ex_nano` `[pynetauto@localhost ~]$ cd ex_nano` `[pynetauto@localhost ex_nano]$ ls` `[pynetauto@localhost ex_nano]$ pwd` `/home/pynetauto/ex_nano` 2. 如果执行如下所示的命令`nano myfile02.txt`,如果同名文件不存在,将打开一个新文件。如果存在同名文件,它将重新打开现有文件。 Nano 有一个伪图形化的用户界面,只要你打开一个文件,帮助菜单就会显示在底部,让你感觉像在家里一样;你想直接投入使用它。如果你习惯了 Windows 上的记事本,使用这个用户友好的工具将不会有问题。 目标:使用`nano`命令创建一个新文件。 `nano filename ↲` `[pynetauto@localhost ex_nano]$ nano myfile02.txt` `GNU nano 2.9.8           myfile02.txt` `|` `^G Get Help ^O Write Out  ^W Where Is ^K Cut Text  ^J Justify   ^C Cur Pos` `^X Exit   ^R Read File  ^\ Replace  ^U Uncut Text ^T To Spell  ^_ Go To Line` 3. 在打开一个新文件并立即决定退出 nano 而不编辑该文件后,该文件将被丢弃。要退出 nano,请使用`^X`(Ctrl+X 键)。如果你想先创建一个新文件,使用`touch myfile02.txt`然后用 nano `myfile02.txt`命令打开它。在 nano 中,如果你看到`^`符号,那意味着 Ctrl 键。 目标:使用`touch`命令创建一个新文件。 `[pynetauto@localhost ex_nano]$ nano myfile02.txt` `[pynetauto@localhost ex_nano]$ ls` `[pynetauto@localhost ex_nano]$ touch myfile02.txt` `[pynetauto@localhost ex_nano]$ ls` `myfile02.txt` 4. 现在重新运行`nano myfile02.txt`命令并输入几行文本。正如您已经发现的,nano 编辑器不要求您像在 vi 编辑器中那样按 I 键进入`-- INSERT --`模式。完成后,要退出 nano,必须按 Ctrl+X ( `^X`)。 目标:写入新文件并退出。 `GNU nano 2.9.8           myfile02.txt           Modified` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` `Save modified buffer? (Answering "No" will DISCARD changes.)` `Y Yes` `N No      ^C Cancel` 5. 要保存文件,请按 Y(是),然后按 Enter 键。或者忽略更改,请按 N(否)。若要返回文件进行进一步编辑,请按^C (Ctrl+C)。在 Linux 操作系统上,大部分程序都是区分大小写的,但 nano 菜单是个例外;它不区分大小写,所以可以互换使用 Y 表示 Y,N 表示 N。 目标:保存已更改的文件。 `Y ↲` `GNU nano 2.9.8           myfile02.txt           Modified` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` `File Name to Write: myfile02.txt` `^G Get Help     M-D DOS Format   M-A Append     M-B Backup File` `^C Cancel      M-M Mac Format   M-P Prepend     ^T To Files` 6. 使用`more`、`less`或`cat`命令,后跟保存的文件名,`myfile02.txt.`如果您使用了`less myfile02.txt`命令,您可以使用 Q 键退出`less.` 目的:使用`cat`、`more`或`less`查看文件内容。 `[pynetauto@localhost ex_nano]$ ls -l` `total 4` `-rw-rw-r--. 1 pynetauto pynetauto 127 Jan 3 01:27 myfile02.txt` `[pynetauto@localhost ex_nano]$ more myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` 您刚刚学习了如何在 nano 中创建/打开、保存和关闭文件。Linux 中有许多优秀的文本编辑器,比如 emacs、gedit、leafpad 和 Komodo,但是这些文本编辑器不会在本章或本书中讨论。当你有空闲时间的时候,你应该从谷歌搜索中探索其他基于 Linux 的文本编辑工具。 #### 用 nano 重新打开文件 1. 要在 nano 中重新打开一个现有的文本文件,您可以使用`nano [filename]`,就像上一个练习中一样。因此,键入`nano myfile02.txt`打开一个现有的文件。 目标:重新打开一个现有文件。 `[pynetauto@localhost ex_nano]$ nano myfile02.txt` `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` `[ Read 5 lines ]` `^G Get Help ^O Write Out  ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File  ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` #### 用 nano 剪切(删除)和粘贴 1. 在 nano 中工作时,要剪切一行,首先移动到要剪切(或复制)的行,然后使用 Ctrl+K 组合键粘贴(取消剪切)使用 Ctrl+U 组合键。如果不粘贴剪切的信息,计算机内存中的信息将被丢弃,其作用类似于删除。 重新打开`myfile02.txt`并将光标放在第 4 行。 目的:在 nano 中剪切(删除)一行。 `[pynetauto@localhost ex_nano]$ nano myfile02.txt` `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` `[ Read 5 lines ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text   ^J Justify   ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text  ^T To Spell   ^_ Go To Line` 要删除第 4 行,请按 Ctrl+K。第 4 行对我们来说似乎已经被删除了,但 nano 会将第 4 行剪切并放入您的服务器内存中。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year` `.` `1\. IOS upgrade` `2\. Network monitoring` `|4\. Life-cycle management` `# cursor line` `[ Read 5 lines ]` `^G Get Help ^O Write Out  ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File  ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 2. 如果要复制和粘贴特定的字符串,首先将光标移动到要开始复制的第一个位置。在这个例子中,我们想要从第 4 行中删除单词`Life-cycle`。 目标:学习如何复制和粘贴一个特定的字符串。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `4\. |Life-cycle management` `# cursor line` `[ Read 5 lines ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 然后按 Ctrl+6 并使用右箭头[→]突出显示单词或字符串。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `4\. Life-cycle| management` `# cursor line` `[ Read 5 lines ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 然后按 Ctrl+K 剪切字符串。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `4\. | management` `# cursor line` `[ Read 5 lines ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 3. 现在使用箭头键将光标移动到第 3 行,并剪切第 3 行。我们想把 3 号线移到 2 号线。按 Ctrl+K 复制该行。 目的:使用 nano 中的剪切粘贴功能。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `|2\. Network monitoring` `# cursor line` `4\. management` `[ Read 5 lines ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replac  ^U Uncut Text ^T To Spell ^_ Go To Line` 现在将光标移动到第 2 行。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `|1\. IOS upgrade` `# cursor line` `4\. management` `[ Read 5 lines ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 按 Ctrl+U 粘贴(插入)复制的行。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `2\. Network monitoring` `|1\. IOS upgrade` `# cursor line` `4\. management` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 4. 如果要剪切多行,请将光标放在第一个位置,并多次按 Ctrl+K 键。复制的多行将被存储在计算机存储器中。我已经把光标放在第 2 行的开头,然后连续按 Ctrl + K ↲三次。 目的:在 nano 中剪切和粘贴多行。 `Position the cursor in the first line to cut.` `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `|2\. Network monitoring` `1\. IOS upgrade` `# cursor line` `4\. management` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 多次使用 Ctrl+K 来剪切三行。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `|` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 现在光标应该定位在第二行;按 Ctrl+U 键两次,粘贴复制两次的三行。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `2\. Network monitoring` `1\. IOS upgrade` `4\. management` `2\. Network monitoring` `1\. IOS upgrade` `4\. management` `|` `# cursor line` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 使用 Ctrl+X 并按 N 放弃更改并退出 nano。当您再次打开文件时,文件的内容应该保持在原始文件中。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justif  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` #### 字符串搜索和替换字符串 1. 在 nano 中,您可以使用 Ctrl+W 来搜索字符串,并且搜索不区分大小写。在下面的例子中,您将搜索单词 *backup* 。 目标:在 nano 中搜索特定的单词或字符串。 按 Ctrl+W,然后键入单词`backup` ↲ `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` `Search: backup|` `# cursor line` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 只要你按下回车键,光标就会移动到单词 *backup* 。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration |backup` `# cursor line` `4\. Life-cycle management` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justif  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 2. 要搜索一个字符串并替换匹配的字符串,可以使用`^\` (Ctrl+\)。 目的:在 nano 中搜索和替换单词。 `Ctrl+\` 首先,让我们追加第五项,“5。设备数据库管理",在我们的列表中如下: `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` `5\. Device database management|` `# cursor line` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 使用 Ctrl+\搜索一个字符串,在本例中是一个单词: *management* 。我们想把这个词换成*行政*。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` `5\. Device database management` `Search (to replace): management|                    # cursor line` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 输入替换单词 *administration* ,然后按键盘上的 Enter 键。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` `5\. Device database management` `Replace with: administration` `^G Get Help        ^Y First Line       ^P PrevHstory` `^C Cancel         ^V Last Line       ^N NextHstory` nano 会突出显示搜索词*管理*的第一个实例。我们想用*管理*替换所有匹配,所以选择 a `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle management` `5\. Device database management` `Replace this instance?` `Y Yes      A All` `N No      ^C Cancel` 所有实例都将被替换,如下所示: `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle administration` `5\. Device database administration` `[ Replaced 2 occurrences ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 3. 您可以使用^O (Ctrl+O)然后使用 Enter 键保存更改,而无需关闭 nano 编辑器。该命令的作用类似于“保存”按钮。 目标:使用 Ctrl+O 选项保存文件。 `Ctrl+O`ⅱ `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle administration` `5\. Device database administration` `File Name to Write: myfile02.txt` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` #### 为 Python 编码定制 nano 当使用 nano 在 Python 中编码时,您会注意到一些缺失的特性,例如避免按空格键四次的四空格制表符和快速告知当前工作位置的行号选项。要定制这些设置,您必须更改`/etc/nanorc`文件中的几个设置。 1. 要在 nano 中将制表符从八个空格改为四个空格,请执行以下步骤: 使用`sudo nano /etc/nanorc`打开一个`nanorc`文件。如果提示输入密码,请输入`sudoer`密码。 目的:在 nano 中为 Python 编码启用四空格跳转。 使用带有键`set tabsize`的`^W` (Ctrl+W)命令搜索 nanorc 文件。 `GNU nano 2.9.8           /etc/nanorc` `## Allow nano to be suspended.` `# set suspend` `## Use this tab size instead of the default; it must be greater than 0.` `# |set tabsize 8` `## Convert typed tabs to spaces.` `# set tabstospaces` `Search: set tabsize` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text  ^J Justify  ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text ^T To Spell ^_ Go To Line` 删除#(哈希)以激活此配置设置;把 8 改成 4。然后添加下一行`set tabstospaces`,如下所示。确保保存更新的`nanorc`配置文件。 `GNU nano 2.9.8           /etc/nanorc` `## Allow nano to be suspended.` `# set suspend` `## Use this tab size instead of the default; it must be greater than 0.` `set tabsize 4` `set tabstospaces|` `## Convert typed tabs to spaces.` `# set tabstospaces` `[ line 172/279 (61%), col 3/16 (18%), char 5826/9448 (61%) ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text   ^J Justify   ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text  ^T To Spell   ^_ Go To Line` 现在是时候检查更改了,重新打开`myfile02.txt`,并使用 Tab 键在第 3–6 行添加四个空格进行确认。每次使用 Tab 键时,您都为正在运行的 Python 代码添加了四个空格。第二行增加了四个空格,第三行增加了八个空格,第四行增加了十二个空格。 `GNU nano 2.9.8           myfile02.txt` `List of tasks to be automated this year.` `1\. IOS upgrade` `2\. Network monitoring` `3\. Configuration backup` `4\. Life-cycle administration` `|5\. Device database administration` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text   ^J Justify   ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text  ^T To Spell   ^_ Go To Line` 如果想撤销之前的更改,可以打开`/etc/nanorc`文件,将`#`添加到两行并保存。 2. 使用`sudo nano /etc/nanorc`再次打开 nanorc 文件。搜索`set linenumbers`并删除该行开头的`#`符号,保存更改。 目的:在 nano 中启用行号。 `[pynetauto@localhost ex_nano]$ sudo nano /etc/nanorc` `[sudo] password for pynetauto: **********` 拆下`set linenumbers`前面的`#`;这将启用 nano 中的行编号。 `GNU nano 2.9.8           /etc/nanorc           Modified` `## Remember the used search/replace strings for the next session.` `# set historylog` `## Display line numbers to the left of the text` `.` `set linenumbers` `## Enable vim-style lock-files. This is just to let a vim user know you` `## are editing a file [s]he is trying to edit and vice versa. There are` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text   ^J Justify   ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text  ^T To Spell   ^_ Go To Line` 在 nano 中打开`myfile02.txt`,检查行号是否可见。 `GNU nano 2.9.8           myfile02.txt` `1 List of tasks to be automated this year.` `2 1\. IOS upgrade` `3   2\. Network monitoring` `4     3\. Configuration backup` `5       4\. Life-cycle administration` `6         5\. Device database administration` `7` `[ Read 6 lines ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text   ^J Justify   ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text  ^T To Spell   ^_ Go To Line` 如果您想撤销更改,您可以打开`/etc/nanorc`文件并将`#`添加回同一行并保存。 起初,vi 和 nano 文本编辑器对 Windows 用户来说很难使用,但这是因为你不熟悉这些程序。随着你花更多的时间使用这些应用,你会变得更加舒适。所以,慢慢来,但千万不要放弃这些应用。如果您知道如何正确使用 vi 文本编辑器,那么您已经掌握了 Linux 管理的重要部分。这是我爸的笑话,但是说的太对了:“学习 Linux 管理的前半部分就是学习如何使用 vi。”当然,nano 文本编辑器支持更多的功能。如果你想了解更多关于其他命令的信息,请参考这个链接: [`https://www.cheatography.com/pepe/cheat-sheets/nano/`](https://www.cheatography.com/pepe/cheat-sheets/nano/) 。 许多读者可能不喜欢 Linux 章节,因为这涉及到用户远离 GUI,在终端控制台上呆上几个小时,但是这是一个非常重要的过程,需要在 Linux 中适应,以后在 Linux 上用 Python 编写代码时也是如此。 ## Linux 基本管理 既然您已经熟悉了 vi 和 nano 文本编辑器,那么让我们学习一些基本的 Linux 管理来帮助您入门。 ### 更改主机名 在第 5 和 6 章节中,你安装了带有桌面选项的 Ubuntu 20 和 CentOS 8.1 服务器,但是 CentOS 8.1 服务器上的服务器名称(`hostname`)被保留为`localhost`。相比之下,在 Ubuntu Server 20 上,服务器名称(`hostname`)是在安装操作系统期间配置的。在本节中,让我们将 CentOS 服务器的主机名更改为`centos8s1`,这赋予了服务器更多的含义。虽然桌面可用,但我们将通过终端控制台更新主机名。以下主机名更改过程适用于 Red Hat (CentOS)和 Debian (Ubuntu) Linux 服务器: 1. 首先,让我们学习如何检查主机名。有几种方法可以检查当前分配给服务器的主机名,这里是一些 Linux 命令。如果您检查的不仅仅是主机名本身,那么`hostnamectl`命令会提供最有用的信息。此外,包含该名称的文件位于`/etc/hostname`文件下。所有这些命令都是 Linux 命令,所以它们区分大小写。 目标:学习如何在 Linux 上使用命令行更改主机名。 `[pynetauto@localhost ~]$ hostname` `localhost` `[pynetauto@localhost ~]$ echo "$HOSTNAME"` `localhost` `[pynetauto@localhost ~]$ printf "%s\n" $HOSTNAME` `localhost` `[pynetauto@localhost ~]$ hostnamectl` `Static hostname: localhost` `Icon name: computer-vm` `Chassis: vm` `Machine ID: e53c131bb54c4fb1a835f3aa435024e2` `Boot ID: 9acca137c1f04ca0ae0db3b6b28db63e` `Virtualization: vmware` `Operating System: CentOS Linux 8 (Core)` `CPE OS Name: cpe:/o:centos:centos:8` `Kernel: Linux 4.18.0-193.14.2.el8_2.x86_64` `Architecture: x86-64` `[pynetauto@localhost ~]$ cat /etc/hostname` `localhost` 2. 使用`sudo hostnamectl set-hostname [new_name]`更新服务器主机名。 目标: `[pynetauto@localhost ~]$ sudo hostnamectl set-hostname centos8s1` `[sudo] password for pynetauto:` `[pynetauto@localhost ~]$` 如果您使用诸如 nano 之类的文本编辑器,您可以打开`/etc/hosts`文件,并使用 Ctrl+\方法将当前主机名(`localhost`)更新为新的服务器名称。在本例中,新的主机名是`centos8s1`。 `GNU nano 2.9.8           /etc/hosts` `1 127.0.0.1  localhost localhost.localdomain localhost4 localhost4.localdomain4` `2 ::1     localhost localhost.localdomain localhost6 localhost6.localdomain6` `[ Read 2 lines ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text   ^J Justify   ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text  ^T To Spell   ^_ Go To Line` 使用 nano 的 Ctrl+\将所有`localhost`实例更改为`centos8s1`。 `GNU nano 2.9.8           /etc/hosts` `1 127.0.0.1  localhost localhost.localdomain localhost4 localhost4.localdomain4` `2 ::1     localhost localhost.localdomain localhost6 localhost6.localdomain6` `Search (to replace) : localhost` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text   ^J Justify   ^C Cur Pos` `^X Exit   ^R Read File ^\ Replace  ^U Uncut Text  ^T To Spell   ^_ Go To Line` `GNU nano 2.9.8           /etc/hosts` `1 127.0.0.1  localhost localhost.localdomain localhost4 localhost4.localdomain4` `2 ::1     localhost localhost.localdomain localhost6 localhost6.localdomain6` `Replace with: centos8s1` `^G Get Help        ^Y First Line       ^P PrevHstory` `^C Cancel         ^V Last Line       ^N NextHstory` `GNU nano 2.9.8           /etc/hosts` `1 127.0.0.1  localhost localhost.localdomain localhost4 localhost4.localdomain4` `2 ::1     localhost localhost.localdomain localhost6 localhost6.localdomain6` `Replace this instance? A` `Y Yes      A All` `N No      ^C Cancel` 修改主机文件后,它应该类似于以下内容: `GNU nano 2.9.8        /etc/hosts                Modified` `1 127.0.0.1  centos8s1 centos8s1.localdomain centos8s14 centos8s14.localdomain4` `2 ::1     centos8s1 centos8s1.localdomain centos8s16 centos8s16.localdomain6` `[ Replaced 8 occurences ]` `^G Get Help ^O Write Out ^W Where Is ^K Cut Text   ^J Justify   ^C Cur Pos` `^X Exit     ^R Read File  ^\ Replace   ^U Uncut Text^T To Spell ^_ Go To Line` 现在使用`sudo reboot`命令重启服务器,让新的主机名生效。 `[pynetauto@localhost ~]$ sudo reboot` `[sudo] password for pynetauto: **********` 当您的 CentOS 服务器重新启动时,使用 PuTTY 通过 SSH 进入服务器,您应该会看到主机名更改为`centos8s1.` `login as: pynetauto` `pynetauto@192.168.183.130's password: **********` `Activate the web console with: systemctl enable --now cockpit.socket` `Last login: Mon Jan 4 11:23:12 2021 from 192.168.183.1` `[pynetauto@centos8s1 ~]$` 3. 请注意,Ubuntu 的主机名已经在安装过程中设置好了,您不必在这里执行任何其他操作。 `pynetauto@ubuntu20s1:~$ hostname` `ubuntu20s1` `pynetauto@ubuntu20s1:~$ echo "$HOSTNAME"` `ubuntu20s1` `pynetauto@ubuntu20s1:~$ printf "%s\n" $HOSTNAME` `ubuntu20s1` `pynetauto@ubuntu20s1:~$ hostnamectl` `Static hostname: ubuntu20s1` `Icon name: computer-vm` `Chassis: vm` `Machine ID: ea202235f9834d979436db10d89b1347` `Boot ID: 6a4035617bf348cba128be8e9d29d6a9` `Virtualization: vmware` `Operating System: Ubuntu 20.04.1 LTS` `Kernel: Linux 5.4.0-47-generic` `Architecture: x86-64` `pynetauto@ubuntu20s1:~$ cat /etc/` `hostname` `ubuntu20s1` ### Linux 基本文件和目录命令 我们将首先学习管理 Linux 操作系统的基本文件和目录的必要命令。深入理解 Linux 命令通常需要很多时间。这本书不是为一般的 Linux 管理而写的,所以本章的目的是帮助你建立 Linux 管理的基础,这样你就可以在 Linux 系统上轻松地执行基本的任务。这里只介绍最基本的命令。在开始练习之前,请花时间查看表 7-5 中的每个命令。您不必马上记住所有的命令,但是可以试着熟悉一些命令语法。 表 7-5。 Linux 文件和目录基本命令 | 命令 | 描述和示例 | | --- | --- | | `pwd` | 当前工作目录`$pwd` | | `ls` | 列表段:列出当前工作目录中的文件和目录`$ ls` | | `ls [directory]` | `$ ls /home/pynetauto/Documents` | | `ls –a` | 列出所有文件,包括隐藏的目录和文件`$ ls -a .bashrc` | | `dir` | 目录`$dir` | | `mkdir` | 制作目录`$ mkdir myapps` | | `cd` | 更改目录`$ cd myapps``$ cd /usr/local` | | `cd ..` | 将当前目录更改为父目录。`/myapps$ cd ..` | | `cd ~` | 从任何地方移动到用户的主目录。`$ cd /usr/sbin``:/usr/sbin$ cd ~``$ pwd``/home/pynetauto` | | `cd -` | 切换回之前工作的目录。`$ cd /usr/local``:/usr/local$ cd -``/home/pynetauto` | | `rm OR rm –r` | 删除文件。`/myapps$ rm router_app.py` | | `rmdir` | 删除目录`$ rmdir myapps` | | `mv` | 重命名文件名。`$ mv file01.py myfile01.py` | | `mv file directory` | 将文件移动到另一个目录。`$ mv file01.py ./Documents` | | `mv directory_A directory_B` | 将一个目录重命名为另一个目录。`$ mv myapps myscripts` | | `touch filename` | 创建文件。`$ touch myfile04.txt``$ touch myfile05.txt myfile06.txt` | | `cp` | 将文件复制到另一个目录,或者用另一个文件名复制并粘贴。`$ cp myfile01.py /home/pynetauto/Documents` | | `cp –a directory_A directory_B` | 将一个目录复制到另一个目录。`$ cp -a Documents Mydocs` | | `find` | 找个文件。`$ find /home/pynetauto -name "myfile*"` | | `grep` | 从文件中搜索字符串。`$ grep pynetauto /etc/passwd``$ grep 'Python' /home/pynetauto/myfile10.py` | ### Linux 文件和目录练习 现在让我们通过一些练习来学习 Linux 基本的文件和目录管理。 到目前为止,之前的练习都是在 CentOS 服务器上完成的。在下面的练习中,将使用 Ubuntu Server 20 LTS 服务器。这次请以 root 用户身份登录到您的 Ubuntu 虚拟机并完成练习。如果你是一个经验丰富的 Linux 管理员,你可以跳到下一章,开始在 CentOS 8.1 上安装 IP 网络应用。对于任何以粗体突出显示的命令,您必须在 Linux 控制台终端中键入它们。 ```py login as: root root@192.168.183.132's password: ********** Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-47-generic x86_64) [omitted for brevity] Last login: Sun Jun 7 08:37:49 2020 from 192.168.183.1 root@ubuntu20s1:~# ``` #### 练习 7-1 1. 使用`pwd`命令检查当前工作目录。正如您在上一节中了解到的,`pwd`代表当前工作目录。可以把它看作是您在 Linux 系统中导航的指南针。 `root@ubuntu20s1:~# pwd` `/root` 2. `ls`是一个特定于 Unix/Linux 的命令,用于查看文件和目录。稍后您将详细了解`ls`命令选项。用`mkdir`命令创建一个目录,并用`ls`列出新目录。 `root@ubuntu20s1:~# mkdir directory1` `root@ubuntu20s1:~# ls` `Desktop   Documents Music   Pictures snap    Videos` `directory1 Downloads myscript Public  Templates` 3. 使用`ls directory_name`命令查看目录中的文件和子目录。继续上一个练习,使用`touch`命令在`directory1`下创建一个文件,然后使用`ls`命令查看在`directory1`下新创建的文件。 `root@ubuntu20s1:~# touch directory1/myfile01.txt` `root@ubuntu20s1:~# ls directory1/` `myfile01.txt` 4. `ls –d`命令指示`ls`简单地列出目录条目而不是内容。您可以使用`ls –l`命令来查看文件和目录的详细信息。`l`代表长列表。现在比较三个输出:`ls`、`ls -d`和`ls –l`。 `root@ubuntu20s1:~# ls directory1` `myfile01.txt` `root@ubuntu20s1:~# ls -d directory1` `directory1` `root@ubuntu20s1:~# ls -l directory1` `total 0` `-rw-r--r-- 1 root root 0 Jan 4 01:39 myfile01.txt` 5. 使用`ls –a`命令一起显示隐藏的文件和文件夹。`a`代表所有,甚至显示隐藏的目录和文件。 `root@ubuntu20s1:~# ls -a` `.       .cache   Documents Music   Public Templates` `..       .config   Downloads myscript .rpmdb Videos` `.bash_history Desktop   .gnupg   Pictures snap` `.bashrc    directory1 .local   .profile .ssh` 6. 您已经使用这个命令几次了;您可以使用`mkdir`命令创建一个新目录。目录类似于 Windows 操作系统中的文件夹;它可以包含子目录和文件。再做一个目录,得到更多的练习。 `root@ubuntu20s1:~# mkdir directory2` `oot@ubuntu20s1:~# ls -d directory*` `directory1 directory2` 7. `cp –a`命令允许您复制整个目录,包括原始目录中的文件和子目录。在本练习中,您将复制`directory1`来创建另一个包含相同`myfile01.txt`的目录。 `root@ubuntu20s1:~# cp -a directory1 directory3` `root@ubuntu20s1:~# ls -d directory*` `directory1 directory2 directory3` `root@ubuntu20s1:~# ls directory*` `directory1:` `myfile01.txt` `directory2:` `directory3:` `myfile01.txt` 8. `rmdir`将允许您删除一个空目录;该命令将删除整个目录。`directory2`是空的,所以使用`rmdir`命令删除它。 `root@ubuntu20s1:~# ls directory2/` `root@ubuntu20s1:~# rmdir directory2` `root@ubuntu20s1:~# ls -d directory*` `directory1 directory3` 9. 如果要删除包含其他文件和目录的目录,可以使用`rm –rf directory_name`。应谨慎使用`rm –rf`命令,因为该选项不会提示用户确认并立即删除项目。 `root@ubuntu20s1:~# ls directory3` `myfile01.txt` `root@ubuntu20s1:~# rm directory3` `rm: cannot remove 'directory3': Is a directory` `root@ubuntu20s1:~# rm -rf directory3` `root@ubuntu20s1:~# ls -d directory*` `directory1` 10. `mv`命令可用于移动或重命名文件和目录。您也可以使用此命令将文件或目录移动到另一个目录;让我们通过下面的练习来了解这一点: `# Renaming a directory` `root@ubuntu20s1:~# ls -d directory*` `directory1` `root@ubuntu20s1:~# mv directory1 directory5` `root@ubuntu20s1:~# ls -d directory*` `directory5` `# Moving a directory to another directory` `root@ubuntu20s1:~# mkdir directory3` `root@ubuntu20s1:~# ls -d directory*` `directory3 directory5` `root@ubuntu20s1:~# mv directory3 directory5` `root@ubuntu20s1:~# ls -d directory*` `directory5` `root@ubuntu20s1:~# ls directory5` `directory3 myfile01.txt` ![img/492721_1_En_7_Figh_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_7_Figh_HTML.gif)Linux 中有没有更好的一目了然的目录和文件的方法? 是的,在 Linux 中有一个方便的目录工具叫做`tree`。安装`tree`后,在当前工作目录下输入`tree`命令,以树形格式显示目录和文件。使用`apt install tree`命令安装软件。 `root@ubuntu20s1:~# apt-get install tree` `Reading package lists... Done` `Building dependency tree` `Reading state information... Done` `The following NEW packages will be installed:` `tree` `0 upgraded, 1 newly installed, 0 to remove and 125 not upgraded.` `[...omitted for brevity]` `Preparing to unpack .../tree_1.8.0-1_amd64.deb ...` `Unpacking tree (1.8.0-1) ...` `Setting up tree (1.8.0-1) ...` `Processing triggers for man-db (2.9.1-1) ...` `root@ubuntu20s1:~# tree` `.` ■t0] ■t0] `│``directory3` `│ └── myfile01.txt` ■t0] ■t0] ■t0] ■t0] ■t0] ■t0] `│``docker` `│ │``423` `│ │``common` `│ │ └── current -> 423` `│ └── lxd` `│``14804` `│``15223` `│``common` `│  └── current -> 15223` ■t0] `└── Videos` Note 如果你不能在 Ubuntu 上安装`tree`,你可能需要启用 universe 库。在安装`tree`之前,在你的 Ubuntu 上运行以下命令: `root@ubuntu20s1:~#``sudo add-apt-repository "deb``http://archive.ubuntu.com/ubuntu` `root@ubuntu20s1:~#` `apt update` `root@ubuntu20s1:~#` `apt-get install tree` #### 练习 7-2 1. 使用`ls -d $PWD/*`命令显示工作目录的路径。 `root@ubuntu20s1:~# ls -d $PWD/*` `/root/Desktop   /root/Downloads /root/Pictures /root/Templates` `/root/directory5 /root/Music   /root/Public  /root/Videos` `/root/Documents  /root/myscript  /root/snap` 2. `cd`、`cd ..`、`cd ~`改变目录命令可以用来移动用户的工作目录位置。进行一些练习,你会在处理文件和目录时节省很多时间。让我们看一些例子,并贯彻到底。 让我们使用`mkdir`命令创建多个目录。 `root@ubuntu20s1:~# mkdir directory1 directory2 directory4` `root@ubuntu20s1:~# ls -d directory*` `directory1 directory2 directory4 directory5` `root@ubuntu20s1:~# mv directory2 directory4` `root@ubuntu20s1:~# ls -d directory*` `directory1 directory4 directory5` `root@ubuntu20s1:~# tree directory*` `directory1` `directory4` ε──t0″ `directory5` ■t0] ε──t0″ `2 directories, 1 file` 键入`cd`(更改目录)并按回车键,您总是会返回到用户的主目录。移动到`root/directory4/directory2`并使用`cd`返回到 root 的工作文件夹。注意使用快捷键`./`来指定当前工作目录。`./directory4/directory2`是`/root/directory4/directory2`的简称。 `root@ubuntu20s1:~# pwd` `/root` `root@ubuntu20s1:~# cd ./directory4/directory2` `root@ubuntu20s1:~/directory4/directory2# pwd` `/root/directory4/directory2` `root@ubuntu20s1:~/directory4/directory2# cd` `root@ubuntu20s1:~# pwd` `/root` 3. `cd ..`命令将工作目录从子目录上移一级。这次让我们向下移动到`/root/directory5/directory3`并使用`cd ..`命令向上移动到父目录。请注意,您还可以使用`cd ../../`来跳多级。 `root@ubuntu20s1:~# cd /root/directory5/directory3` `root@ubuntu20s1:~/directory5/directory3# pwd` `/root/directory5/directory3` `root@ubuntu20s1:~/directory5/directory3# cd ..` `root@ubuntu20s1:~/directory5# pwd` `/root/directory5` `root@ubuntu20s1:~/directory5# cd ..` `root@ubuntu20s1:~# pwd` `/root` `root@ubuntu20s1:~# cd ./directory5/directory3` `root@ubuntu20s1:~/directory5/directory3# pwd` `/root/directory5/directory3` `root@ubuntu20s1:~/directory5/directory3# cd ../../` `root@ubuntu20s1:~# pwd` `/root` 4. 如果您知道确切的目录路径,您可以通过键入`cd`后跟特定的目录路径,从一个工作目录跳到下一个目录。下面的练习显示了从父`directory5`下的`directory3`跳到父`directory4`下的`directory2`: `root@ubuntu20s1:~# cd ./directory5/directory3` `root@ubuntu20s1:~/directory5/directory3# pwd` `/root/directory5/directory3` `root@ubuntu20s1:~/directory5/directory3# cd` `/root/directory4/directory2` `root@ubuntu20s1:~/directory4/directory2# pwd` `/root/directory4/directory2` 5. `cd ~`也可用于返回用户的主目录。 `root@ubuntu20s1:~# cd /root/directory4/directory2` `root@ubuntu20s1:~/directory4/directory2# pwd` `/root/directory4/directory2` `root@ubuntu20s1:~/directory4/directory2# cd ~` `root@ubuntu20s1:~# pwd` `/root` 6. 与`./`命令不同,`cd ~`还允许您在不依赖于当前工作目录的目录之间跳转。 `root@ubuntu20s1:~# cd /root/directory4/directory2` `root@ubuntu20s1:~/directory4/directory2# pwd` `/root/directory4/directory2` `root@ubuntu20s1:~/directory4/directory2# cd ~/directory5/directory3` `root@ubuntu20s1:~/directory5/directory3# pwd` `/root/directory5/directory3` 7. `dir`(目录)命令与`ls –C –b`命令相同,据说 Linux 和 Windows 用户都包括了这个命令。不同的是,当你使用`dir`时,目录名是黑色的,但是使用`ls –C –b`会返回彩色的目录名。 `root@ubuntu20s1:~# dir` `Desktop   directory4 Documents Music   Public Templates` `directory1 directory5 Downloads Pictures snap  Videos` `root@ubuntu20s1:~# ls -C -b` `Desktop   directory4 Documents Music   Public Templates` `directory1 directory5 Downloads Pictures snap  Videos` 8. `touch`命令用于创建一个文件。如果您输入如下所示的`touch file_name`,将会创建一个文件。你也可以用一个`touch`命令创建多个文件。 `root@ubuntu20s1:~# touch myfile02.txt` `root@ubuntu20s1:~# ls myfile*` `myfile02.txt` `root@ubuntu20s1:~# touch myfile03.txt myfile04.txt` `root@ubuntu20s1:~# ls myfile*` `myfile02.txt myfile03.txt myfile04.txt` 9. `cp`(复制)命令允许你复制文件或目录。复制目录时不要忘记使用`–r`(递归)选项。 `root@ubuntu20s1:~# ls myfile*` `myfile02.txt myfile03.txt myfile04.txt` `root@ubuntu20s1:~# cp myfile03.txt myfile05.txt` `root@ubuntu20s1:~# ls myfile*` `myfile02.txt myfile03.txt myfile04.txt myfile05.txt` `root@ubuntu20s1:~# ls -d directory*` `directory1 directory4 directory5` `root@ubuntu20s1:~# cp -r directory1 directory2` `root@ubuntu20s1:~# ls -d directory*` `directory1 directory2 directory4 directory5` 10. `rm`(删除)可用于删除文件或删除目录。在下面的例子中,将使用`–r`选项删除`directory4`。 `root@ubuntu20s1:~# ls myfile*` `myfile02.txt myfile03.txt myfile04.txt myfile05.txt` `root@ubuntu20s1:~# rm myfile02.txt` `root@ubuntu20s1:~# ls myfile*` `myfile03.txt myfile04.txt myfile05.txt` `root@ubuntu20s1:~# rm myfile*` `root@ubuntu20s1:~# ls myfile*` `ls: cannot access 'myfile*': No such file or directory` `root@ubuntu20s1:~# tree directory*` `directory1` `directory2` `directory4` ε──t0″ `directory5` ■t0] ε──t0″ `2 directories, 1 file` `root@ubuntu20s1:~# rm -r directory4` `root@ubuntu20s1:~# tree directory*` `directory1` `directory2` `directory5` ■t0] ε──t0″ `1 directory, 1 file` #### 练习 7-3 1. `mv` (move)命令允许您更改文件名或将文件和目录移动到另一个目录。让我们通过一些例子来了解这一点。 `# Rename myfile01.txt in root/directory5 to yourfile01.txt.` `root@ubuntu20s1:~# ls ./directory5/` `directory3 myfile01.txt` `root@ubuntu20s1:~# mv ./directory5/myfile01.txt ./directory5/yourfile01.txt` `root@ubuntu20s1:~# ls ./directory5/` `directory3 yourfile01.txt` `# Move yourfile01.txt to /root directory.` `root@ubuntu20s1:~# mv ./directory5/yourfile01.txt ./` `root@ubuntu20s1:~# ls ./directory5/` `directory3` `root@ubuntu20s1:~# pwd` `/root` `root@ubuntu20s1:~# ls your*` `yourfile01.txt` 2. 现在我们把`directory5`改名为`directory4`。然后把`directory2`套在`directory3`里面,再把`directory1`套进`directory2`。 `root@ubuntu20s1:~# mv directory5 directory4` `root@ubuntu20s1:~# tree directory*` `directory1` `directory2` `directory4` ε──t0″ `1 directory, 0 files` `root@ubuntu20s1:~# mv directory2 ./directory4/directory3/` `root@ubuntu20s1:~# mv directory1 ./directory4/directory3/directory2/` `root@ubuntu20s1:~# tree directory*` `directory4` ε──t0″ ε──t0″ ε──t0″ `3 directories, 0 files` 3. 现在使用`.`(点号)方法,让我们将`directory1`、`directory2`和`directory3`移动到根用户的文件夹,与父目录`directory4.`在同一层 `root@ubuntu20s1:~# tree directory*` `directory4` ε──t0″ ε──t0″ ε──t0″ `3 directories, 0 files` `root@ubuntu20s1:~# mv ./directory4/directory3/directory2/directory1 .` `root@ubuntu20s1:~# mv ./directory4/directory3/directory2 .` `root@ubuntu20s1:~# mv ./directory4/directory3 .` `root@ubuntu20s1:~# tree directory*` `directory1` `directory2` `directory3` `directory4` 4. 现在让我们使用`rm`和`rm –r`命令来清理文件和目录以重置时钟。在本练习中,我们要删除一个文本文件和四个目录。 `root@ubuntu20s1:~# rm yourfile01.txt` `oot@ubuntu20s1:~# ls -d directory*` `directory1 directory2 directory3 directory4` `root@ubuntu20s1:~# rm -r directory*` `root@ubuntu20s1:~# ls -d directory*` `ls: cannot access 'directory*': No such file or directory` 5. 用您的名字和一个`secrete`密码创建`mysecrete`文件。使用`.`(点)方法创建一个隐藏目录`hideme`,并将`secrete`文件移动到`.hideme`目录中。要查看隐藏文件,使用带有`ls`命令的`–a`选项。 `root@ubuntu20s1:~# nano mysecrete` `root@ubuntu20s1:~# cat mysecrete` `pynetauto` `fairdinkum` `root@ubuntu20s1:~# ls my*` `mysecrete` `root@ubuntu20s1:~# mkdir .hideme` `root@ubuntu20s1:~# ls` `Desktop Documents Downloads Music Pictures Public snap Templates Videos` `root@ubuntu20s1:~# ls -a` `.       .bashrc Desktop  .gnupg  Music   Public Templates` `..       .cache  Documents .hideme Pictures snap  Videos` `.bash_history .config Downloads .local  .profile .ssh` `root@ubuntu20s1:~# mv mysecrete .hideme` `root@ubuntu20s1:~# ls .hideme` `mysecrete` `root@ubuntu20s1:~# ls` `Desktop Documents Downloads Music Pictures Public snap Templates Videos` `root@ubuntu20s1:~# tree .hideme` `.hideme` ε──t0″ `0 directories, 1 file` 如何查看命令选项? 您可以使用`--help`或`man`命令。以下示例显示了`ls --help`和`man ls`命令: `root@ubuntu20s1:~# ls --help` `root@ubuntu20s1:~# man ls` 还有,快速搜索 *Linux 命令备忘单*这几个字,Google 会返回上百个搜索结果。这里列出了一个这样的站点供您参考: [`https://cheatography.com/davechild/cheat-sheets/linux-command-line/`](https://cheatography.com/davechild/cheat-sheets/linux-command-line/) #### Linux 文件和目录练习 7-2 您刚刚完成了练习 7-1 中的一个基本 Linux 文件和目录练习。接下来,让我们学习更多的命令。用户友好的 GUI 通常是 enterprise Linux 系统中缺少的功能,管理员的许多任务都是通过命令行或控制台界面来执行的。 您的 Linux 学习成功与否取决于您使用基本的文件和目录命令处理文件和目录的能力。 #### 回声,猫,多,少指挥演习 1. 创建一个名为`greet01`的新文件,使用`echo`命令写一个问题,“你能用不同的语言打招呼吗?”进入`greet01`文件。 `root@ubuntu20s1:~# touch greet01` `root@ubuntu20s1:~# ls greet*` `greet01` `root@ubuntu20s1:~# echo "Can you say Hello in different languages?" > greet01` `root@ubuntu20s1:~# more greet01` `Can you say Hello in different languages?` 2. 创建另一个名为`greet02`的文件。这一次用各种语言编写`Hello`,并再次使用`echo`命令写入文件。在这个练习中,你将学习如何用十种不同的语言说“嗨”。 `root@ubuntu20s1:~# touch greet02` `root@ubuntu20s1:~# echo "Hi Bonjour Nihao Konichiwa Shalom Merhaba Ola Namaste Ciao Anyoung" > greet02` `root@ubuntu20s1:~# cat greet02` `Hi Bonjour Nihao Konichiwa Shalom Merhaba Ola Namaste Ciao Anyoung` 3. 在之前的练习中,`cat`命令用于显示`greet02`的内容。`cat`命令的另一个用途是将多个文件合并成一个文件。在这里,您将合并`greet01`和`greet02`并创建`greet03`文件。 `root@ubuntu20s1:~# cat greet01 greet02 > greet03` `root@ubuntu20s1:~# ls -d greet*` `greet01 greet02 greet03` `root@ubuntu20s1:~# more greet03` `Can you say Hello in different languages?` `Hi Bonjour Nihao Konichiwa Shalom Merhaba Ola Namaste Ciao Anyoung` 4. 如果你想给同一个文件添加更多的内容,使用`cat >>`命令。 `root@ubuntu20s1:~# cat >> greet03` `How do you say "Love" in different languages?` `Love Amour Ai Ai Ahaba Ask Amor Amore Mohabbat Sarang` `#To exit, press Ctrl+D keys.` `root@ubuntu20s1:~# more greet03` `Can you say Hello in different languages?` `Hi Bonjour Nihao Konichiwa Shalom Merhaba Ola Namaste Anyoung` `How do you say "Love" in different languages?` `Love Amour Ai Ai Ahaba Ask Amor Amore Mohabbat Sarang` 5. 您也可以使用`cat >`命令创建一个新文件。 `root@ubuntu20s1:~# cat > greet04` `This is a test file created with the "cat >" command.` `You can write something meaningful or anything you like.` `#To exit, press Ctrl+D keys` `.` `root@ubuntu20s1:~# cat greet04` `This is a test file created with "cat >" command.` 你可以写一些有意义的东西或者任何你喜欢的东西。 6. 您还可以使用更多命令来查看文件内容。如果文件很长,使用空格键向下滚动页面,或使用 Enter 键逐行向下滚动。如果您想在阅读过程中退出,请使用 Ctrl+C 或 Ctrl+Z 键。 `root@ubuntu20s1:~# more greet03` `Can you say Hello in different languages?` `Hi Bonjour Nihao Konichiwa Shalom Merhaba Ola Namaste Anyoung` `How do you say "Love" in different languages?` `Love Amour Ai Ai Ahaba Ask Amor Amore Mohabbat Sarang` 7. 您也可以使用`less`命令来查看文件的内容,但是`less`命令不会在终端上打印内容。若要向下滚动页面,请使用空格键;若要逐行向下滚动,请使用 Enter 键。要退出,使用 Ctrl+Z(或 Ctrl+C,然后是`:q`)。 `root@ubuntu20s1:~# less greet03` `Can you say Hello in different languages?` `Hi Bonjour Nihao Konichiwa Shalom Merhaba Ola Namaste Anyoung` `How do you say "Love" in different languages` `?` `Love Amour Ai Ai Ahaba Ask Amor Amore Mohabbat Sarang` `greet03 (END)` 哪里可以找到最好的 Linux 学习资料? 通常,生活中最好的东西都是免费的。感谢 Paul Cobbaut 在 Linuxfun 网站上分享了他的书 *Linux Fundamentals* ,更多的人可以享受 Linux 带来的乐趣。我强烈推荐你下载这个免费的 PDF 文件,并把它用在你的优势上。 UR:[`Linux-training . be/Linux fun . pdf`](https://linux-training.be/linuxfun.pdf) #### 带选项的 ls 命令 您可能会想,钻研这些练习似乎离您想要将 Python 网络自动化带到哪里的目标太远了。但是你必须做练习,一次打下一块砖来建立一个坚实的基础。所以,这就是建立强大基础的过程。尝试从章节练习中获得尽可能多的实践,并在线和离线使用其他外部 Linux 命令学习材料。 `ls`枚举文件和目录。 `ls –l`显示文件和目录的详细信息。 `ls –a`列出当前工作目录下的所有文件。 `ls –al`显示当前工作目录中文件和目录的详细信息。 1. 使用带有关键字`search`和全包或贪婪`*.`的`ls`命令。以下示例列出了当前工作目录中以单词`greet`开头的所有文件或文件夹。 `root@ubuntu20s1:~# ls greet*` `greet01 greet02 greet03 greet04` 2. 创建一个名为`greetings`的新目录,运行带有`–d`和`–l`(列表)选项的`ls`命令,比较文件和目录之间的差异。在问候目录的开头,注意到了`d`这封信。这表明这是一个目录。 `root@ubuntu20s1:~# mkdir greetings` `root@ubuntu20s1:~# ls -d -l greet*` `-rw-r--r-- 1 root root  42 Jun 3 11:45 greet01` `-rw-r--r-- 1 root root  62 Jun 3 11:51 greet02` `-rw-r--r-- 1 root root 204 Jun 3 12:10 greet03` `-rw-r--r-- 1 root root 106 Jun 3 12:15 greet04` `drwxr-xr-x 2 root root 4096 Jun 3 12:35 greetings` 3. 此`–a`命令列出所有文件,包括隐藏文件。隐藏文件以`.`(点)开头。 `root@ubuntu20s1:~# ls -a` `.       .cache   Downloads greet03  .local  Public   test01` `..       .config  .gnupg   greet04  Music   snap    Videos` `.bash_history Desktop  greet01  greetings Pictures .ssh` `.bashrc    Documents greet02  .hideme  .profile Templates` 4. 您可以组合各种`ls`选项来列出您喜欢的文件和目录。其他选项有`–R`、`-o`、`-g`、`-i`、`-s`、`-t`、`-S`、`-r`。如果你有一个小时的时间来探索这些选项,请上网花一两个小时找到每一个选项,然后混合搭配。以下是`–l`(列表)和`–h`(人类可读)命令的组合使用: `root@ubuntu20s1:~# ls -lh greet*` `-rw-r--r-- 1 root root  42 Jun 3 11:45 greet01` `-rw-r--r-- 1 root root  62 Jun 3 11:51 greet02` `-rw-r--r-- 1 root root 204 Jun 3 12:10 greet03` `-rw-r--r-- 1 root root 106 Jun 3 12:15 greet04` `greetings` `:` `total 0` #### 一次删除文件和目录 1. 如果您想删除目录中以相同名称开头的所有文件,可以使用`rm filename *.`如果您想删除目录中的所有文件,可以使用`*.`继续上一个练习,让我们将所有以单词`greet`开头的文件移动到`greetings`目录中。 `roroot@ubuntu20s1:~# pwd` `/root` `root@ubuntu20s1:~# ls` `Desktop  Downloads greet02 greet04  Music   Public Templates Videos` `Documents greet01  greet03 greetings Pictures snap  test01` `root@ubuntu20s1:~# ls -lh greet*` `-rw-r--r-- 1 root root  42 Jun 3 11:45 greet01` `-rw-r--r-- 1 root root  62 Jun 3 11:51 greet02` `-rw-r--r-- 1 root root 204 Jun 3 12:10 greet03` `-rw-r--r-- 1 root root 106 Jun 3 12:15 greet04` `greetings:` `total 0` `root@ubuntu20s1:~# mv ./greet0* ./greetings` `root@ubuntu20s1:~# ls` `Desktop  Downloads Music   Public Templates Videos` `Documents greetings Pictures snap  test01` `root@ubuntu20s1:~# ls ./greetings` `greet01 greet02 greet03 greet04` 让我们创建另外四个名为 by01–by04 的文件。 `root@ubuntu20s1:~# cd greetings` `root@ubuntu20s1:~/greetings# touch bye01 bye02 bye03 bye04` `root@ubuntu20s1:~/greetings# ls` `bye01 bye02 bye03 bye04 greet01 greet02 greet03 greet04` 现在让我们再检查一次`greetings`目录中的文件,删除包含特定字符串的文件;我们的目标是以 04 结尾的文件。`*`是替换所有字符的通配符。 `root@ubuntu20s1:~/greetings# ls` `bye01 bye02 bye03 bye04 greet01 greet02 greet03 greet04` `root@ubuntu20s1:~/greetings# find . -name "*04"` `./bye04` `./greet04` `root@ubuntu20s1:~/greetings# rm *04` `root@ubuntu20s1:~/greetings# ls` `bye01 bye02 bye03 greet01 greet02 greet03` 接下来,让我们使用`rm greet*`句柄删除所有包含`greet`的文件。 `root@ubuntu20s1:~/greetings# rm greet*` `root@ubuntu20s1:~/greetings# ls` `bye01 bye02 bye03` 然后,让我们使用`rm *`选项清除文件夹中的所有文件。在这个命令之后,目录应该是空的。 `root@ubuntu20s1:~/greetings# rm *` `root@ubuntu20s1:~/greetings# ls` `root@ubuntu20s1:~/greetings#` 2. 如果想用同样的方法删除目录,就得用`rm –r`。`–r`代表递归;如果你不使用`–r`选项,Linux 不允许你删除目录,但是你必须小心使用它。从同一个目录开始,让我们在它下面创建两个目录,并使用`*`通配符来删除包含特定字符串的目录。要删除包含`hi`的目录,您将使用一个双通配符,一个在前面,一个在后面。在本练习结束时,您的目录应该是空的。 `root@ubuntu20s1:~/greetings# mkdir sayhi01 sayhi02 saybye01 saybye02` `root@ubuntu20s1:~/greetings# ls` `saybye01 saybye02 sayhi01 sayhi02` `root@ubuntu20s1:~/greetings# rm *hi*` `rm: cannot remove 'sayhi01': Is a directory` `rm: cannot remove 'sayhi02': Is a directory` `root@ubuntu20s1:~/greetings# rm -r *hi*` `root@ubuntu20s1:~/greetings# ls` `saybye01 saybye02` `root@ubuntu20s1:~/greetings# rm -r saybye*` `root@ubuntu20s1:~/greetings# ls` `root@ubuntu20s1:~/greetings#` #### 创建一个 Python 应用,使您的文件可执行,添加一个 Shebang,然后运行该应用 您可以使用`chmod`命令更改文件的权限。当更改 Python 脚本文件的属性以使其可执行时,这个 Linux 命令非常方便。根用户目录中当前没有 Python 文件。让我们快速创建一个简单的 Python 脚本,并看看如何使用 Python 脚本: 1. 要在 Linux 上创建 Python 脚本,请在文件名末尾添加一个`.py`文件扩展名。要创建你的 Python 脚本,按照我们在 4.4.2.1 章节中学到的`echo`和`cat`命令,或者简单地使用你在本章中学到的 vi 或 nano 文本编辑器。单词 *Automation* 后面的`\n`表示换行符,增加/改变行。创建这个文件并使用`python3 print5times.py`运行应用,以验证您的脚本运行顺利。 `root@ubuntu20s1:~/myscript# echo "x = 'Python Network Automation\n'" > print5times.py` `root@ubuntu20s1:~/myscript# more print5times.py` `x = 'Python Network Automation\n'` `root@ubuntu20s1:~/myscript# cat >> print5times.py` `print(x*5)` `#To exit, press Ctrl+D keys.` `root@ubuntu20s1:~/myscript# more print5times.py` `x = 'Python Network Automation\n'` `print(x*5)` `root@ubuntu20s1:~/myscript# python3 print5times.py` `Python Network Automation` `Python Network Automation` `Python Network Automation` `Python Network Automation` `Python Network Automation` 2. 现在我们已经创建了一个工作的 Python 脚本(或应用),并查看了文件模式。是的,这个文件也可以被称为一个*应用*,因为它是一个在你每次运行时打印五次`x`的应用。 运行`ls –l print5times.py`查看访问。您必须查看以`-rw-r--r--.`开始的结果,如您所见,它缺少了`x`,或可执行级访问。您需要修改这个文件的属性以包含`x`来使这个文件成为一个可执行文件,这样我们就可以在不将 Python 3 指定为应用的情况下运行应用。 `root@ubuntu20s1:~/myscript# ls -l print5times.py` `-rw-r--r-- 1 root root 43 Jun 6 10:42 print5times.py` `root@ubuntu20s1:~/myscript# ./print5times.py` `-bash: ./print5times.py: Permission denied` 使用`chmod +x print5times.py`使脚本可执行。文件颜色应该自动变为绿色,表明这是一个可执行文件。 `root@ubuntu20s1:~/myscript# chmod +x print5times.py` `root@ubuntu20s1:~/myscript# ls -l print5times.py` `-rwxr-xr-x 1 root root 45 Jun 6 10:46 print5times.py` 尝试在没有`python3`命令的情况下运行应用。但是当你运行脚本时,你会遇到如下意外的 token 错误,因为我们忘记了在顶部添加臭名昭著的 shebang ( `#!/usr/bin/python3`)行;shebang 线定义了解释器的位置。 `root@ubuntu20s1:~/myscript# ./print5times.py` `./print5times.py: line 1: x: command not found` `./print5times.py: line 2: syntax error near unexpected token `x*5'` `./print5times.py: line 2: `print(x*5)'` 3. 让我们在脚本的开头加上`#!/usr/bin/python3` (shebang)。这一次,我们将使用带有`–I`和`–e`选项的`sed`命令。Sed 或流编辑器也是一个强大的 Linux/Unix 文本操作工具。你不必 100%熟悉这个工具,但是只要知道它是如何工作的,总有一天它会派上用场。 `root@ubuntu20s1:~/myscript# cat print5times.py` `x = "Python Network Automation\n"` `print(x*5)` `root@ubuntu20s1:~/myscript# sed -i -e '1i#!/usr/bin/python3\' print5times.py` `root@ubuntu20s1:~/myscript# cat print5times.py` `#!/usr/bin/python3` `x = "Python Network Automation\n"` `print(x*5)` 现在我们检查了 shebang 行在第一行,让我们从工作目录运行 Python 脚本。它应该打印`x`五次,如下所示: `root@ubuntu20s1:~/myscript# ./print5times.py` `Python Network Automation` `Python Network Automation` `Python Network Automation` `Python Network Automation` `Python Network Automation` 4. 如果您想在没有`./?`的情况下运行应用,该怎么办?您可以使用 Linux 命令`PATH="$(pwd):$PATH"`,但是一旦您的 Linux 终端会话结束,添加的路径就会消失。 `root@ubuntu20s1:~/myscript# ls -lh print5times.py` `-rwxr-xr-x 1 root root 64 Jun 6 11:23 print5times.py` `root@ubuntu20s1:~/myscript# PATH="$(pwd):$PATH"` `root@ubuntu20s1:~/myscript# print5times.py` `Python Network Automation` `Python Network Automation` `Python Network Automation` `Python Network Automation` `Python Network Automation` 5. 要将`/root/myscript`目录或当前目录永久添加到`PATH`变量中,可以使用下面的命令。现在,即使在您退出会话并返回之后,您应该仍然能够在没有`./`的情况下运行脚本。添加路径后,确保通过执行 source `~/.bashrc`或`exec` bash 命令重启 bash,以使更改生效。 `root@ubuntu20s1:~/myscript# echo "export PATH=\$PATH:$(pwd)" >> ~/.bashrc` `root@ubuntu20s1:~/myscript# exec bash #(source ~/.bashrc) restarts bash` `root@ubuntu20s1:~/myscript# print5times.py` `Python Network Automation` `Python Network Automation` `Python Network Automation` `Python Network Automation` `Python Network Automation` 你想学习更多的 Linux 并获得认证吗? 如果你想进一步学习 Linux,你需要学习 Linux 职业学院 101 和红帽的 RHCSA/RHCE。对于那些有空闲时间和金钱的人来说,在线和线下都有很多免费和付费的培训项目。如果您对 LPI 和 RHCE 认证课程感兴趣,请参考以下链接。 Linux 职业学院 URL: [`https://www.lpi.org/our-certifications/exam-101-objectives`](https://www.lpi.org/our-certifications/exam-101-objectives) 红帽认证系统管理员(RHCSA)网址: [` www。红帽。com/en/services/certification/rhcsa`](https://www.redhat.com/en/services/certification/rhcsa) 红帽认证工程师: [`https://www.redhat.com/en/services/certification/rhce`](https://www.redhat.com/en/services/certification/rhce) ## 摘要 现在,文件和目录练习就完成了。这里我们只涉及了冰山一角,但是对于您在后面的 Python 网络自动化实验中执行任务来说已经足够了。您必须定期练习 Linux 文件和目录导航,如果不是每天都练习的话,以便习惯它。这就是 Python 网络自动化的神奇之处。 在下一章中,我们将快速介绍一些基本的 Linux 命令来查找重要的 Linux 系统信息。在真实的生产环境中,您将无法幸运地选择您喜欢的 Linux 风格,或者始终拥有对服务器的 root 访问权限。您必须知道如何找到新的 Linux 系统信息,并为您的 Linux 工具、Python 和相关模块找到兼容的软件。 # 八、Linux 基本管理 有很多关于 Linux 主题的书,其中一些书虽然长达数百页,但只关注一个 Linux 发行版。从上一章继续,我们将在这一章讨论最基本的 Linux 主题,这样你就可以看完这本书。如果你是 Linux 的新手,想了解更多关于 Linux 操作系统(OS)的知识,那么使用一本专门的 Linux 书籍是个不错的主意。您将学习查找 Linux 系统信息,使用 Linux 网络命令,然后在 CentOS 8.1 服务器上安装 TFTP、FTP、SFTP 和 NTP 服务。 ![img/492721_1_En_8_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Figa_HTML.jpg) ## Linux 信息:内核和发行版本 大多数新的 Linux 用户都会提出第一个问题,“我如何检查确切的 Linux 发行版本和内核?”Linux 内核是 Linux 操作系统的主要组成部分,是计算机硬件和进程之间的核心接口。你如何在你的操作系统上检查 Linux 内核和发行版本?本章中的练习基于 CentOS(一种基于 Red Hat 的操作系统),由于不同 Linux 发行版的命令语法,使用的一些命令可能无法在基于 Debian 的操作系统(Ubuntu)上工作。如果是这样的话,那么你就会被提醒这样的区别。然而,跨不同 Linux 发行版的大多数 Linux 命令都是相似的。 | ① | 如何检查 Linux 内核版本?使用带有不同选项的`uname`命令;CentOS 和 Ubuntu 上使用的命令与`uname`命令相同。`For CentOS 8.1``[root@centos8s1 ~]# uname``Linux``[root@centos8s1 ~]# uname -o``GNU/Linux``[root@centos8s1 ~]# uname -r``4.18.0-193.14.2.el8_2.x86_64`[root@centos8s1 ~]# `uname -or``4.18.0-193.14.2.el8_2.x86_64 GNU/Linux``For Ubuntu 20.04 LTS``root@ubuntu20s1:~# uname``Linux``root@ubuntu20s1:~# uname -o``GNU/Linux``root@ubuntu20s1:~# uname -r``5.4.0-47-generic``root@ubuntu20s1:~# uname -or``5.4.0-47-generic GNU/Linux` | | ② | 下面的练习是一个额外的`uname`命令练习,它通过名称将空终止的操作系统信息字符串存储到结构化引用中。使用不同的选项运行`uname`命令。注意,同样的命令可以在 Ubuntu 20.04 LTS 服务器上使用。`[root@centos8s1 ~]# uname –a` `# -a option for all``Linux centos8s1 4.18.0-193.14.2.el8_2.x86_64 #1 SMP Sun Jul 26 03:54:29 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux``[root@centos8s1 ~]# uname -n` `# -n for name or hostname``centos8s1``[root@centos8s1 ~]# uname -r` `# -r for Linux kernel version``4.18.0-193.14.2.el8_2.x86_64``[root@centos8s1 ~]# uname -m` `# -m for system architecture``x86_64``[root@centos8s1 ~]# uname -v` `# -v for system time and timezone``#1 SMP Sun Jul 26 03:54:29 UTC 2020``[root@centos8s1 ~]# uname -rnmv` `# combined options``centos8s1 4.18.0-193.14.2.el8_2.x86_64 #1 SMP Sun Jul 26 03:54:29 UTC 2020 x86_64` | | ③ | 在基于 CentOS 和 Red Hat 的 Linux 操作系统上,您还可以使用`grep`命令来检查 Grub 环境块。使用`grep saved_entry /boot/grub2/grubenv`命令。这个命令在基于 Debian 的 Linux 发行版上不起作用。`For CentOS 8.1``[root@centos8s1 ~]# grep saved_entry /boot/grub2/grubenv``saved_entry=e53c131bb54c4fb1a835f3aa435024e2-4.18.0-193.14.2.el8_2.x86_64` | | ④ | 使用`hostnamectl`来检查主机名、操作系统、内核版本、架构等等。`For CentOS 8.1``[root@centos8s1 ~]# hostnamectl``Static hostname: centos8s1``Icon name: computer-vm``Chassis: vm``Machine ID: e53c131bb54c4fb1a835f3aa435024e2``Boot ID: 648950d070634d3e856b015f4a7cf7cd``Virtualization: vmware``Operating System: CentOS Linux 8 (Core)``CPE OS Name: cpe:/o:centos:centos:8``Kernel: Linux 4.18.0-193.14.2.el8_2.x86_64``Architecture: x86-64``For Ubuntu 20.04 LTS``root@ubuntu20s1:~# hostnamectl``Static hostname: ubuntu20s1``Icon name: computer-vm``Chassis: vm``Machine ID: ea202235f9834d979436db10d89b1347``Boot ID: 9bcbe4f6d00e407a81634dd61d74ff39``Virtualization: vmware``Operating System: Ubuntu 20.04.1 LTS``Kernel: Linux 5.4.0-47-generic``Architecture: x86-64` | | ⑤号 | 您也可以使用`lsb_release –d`命令来检查您的操作系统。与 Ubuntu OS 不同,CentOS 没有预装`lsb`。你可以通过使用`yum install –y redhat-lsb`命令或者运行`lsb_release –d`来安装它,当出现提示时,说 Y 来安装。`For CentOS 8.1``[root@centos8s1 ~]# lsb_release -d``[root@centos8s1 ~]# yum install -y redhat-lsb``[root@centos8s1 ~]# lsb_release -d``Description:    CentOS Linux release 8.2.2004 (Core)`您还可以使用`–sc`和`–a`选项来查看关于您的 Linux 操作系统的更多信息。`For Ubuntu 20.04 LTS``root@ubuntu20s1:~# lsb_release -d``Description:    Ubuntu 20.04.1 LTS``root@ubuntu20s1:~# lsb_release -sc``focal``root@ubuntu20s1:~# lsb_release -a``No LSB modules are available.``Distributor ID: Ubuntu``Description:    Ubuntu 20.04.1 LTS``Release:        20.04``Codename:       focal` | | ⑥ | 也可以使用`cat`命令输出`/etc/`目录下的信息。注意,这在 CentOS 上有效,但在 Ubuntu 上*无效,所以查看系统信息的更好方法是使用带有通配符`*`的`cat`。见下一个练习。*`For CentOS 8.1``[root@centos8s1 ~]# cat /etc/centos-release``CentOS Linux release 8.2.2004 (Core)``[root@centos8s1 ~]# cat /etc/system-release``CentOS Linux release 8.2.2004 (Core)` | | ① | 要检查您的 Linux 发行版的版本信息,请使用`cat /etc/*release`命令。这个`cat`命令是一个值得记住的 Linux 命令,因为它提供了关于您的 Linux 操作系统的大量信息。这个命令在 CentOS 和 Ubuntu Linux 操作系统上都有效。`[root@centos8s1 ~]# cat /etc/*release``CentOS Linux release 8.2.2004 (Core)``NAME="CentOS Linux"``VERSION="8 (Core)"``ID="centos"``ID_LIKE="rhel fedora"``VERSION_ID="8"``PLATFORM_ID="platform:el8"``PRETTY_NAME="CentOS Linux 8 (Core)"``ANSI_COLOR="0;31"``CPE_NAME="cpe:/o:centos:centos:8"``HOME_URL="``https://www.centos.org/``BUG_REPORT_URL="``https://bugs.centos.org/``CENTOS_MANTISBT_PROJECT="CentOS-8"``CENTOS_MANTISBT_PROJECT_VERSION="8"``REDHAT_SUPPORT_PRODUCT="centos"``REDHAT_SUPPORT_PRODUCT_VERSION="8"``CentOS Linux release 8.2.2004 (Core)``CentOS Linux release 8.2.2004 (Core)`此外,尝试运行`cat /etc/os-release`命令并检查差异。 | ## 关于 Linux 的信息:使用 netstat 命令验证 TCP/UDP 端口 为了让您为 IP 服务安装做好准备,让您的 Linux 服务器对我们的实验室有用,并在生产中应用您所学的知识,您首先必须知道如何使用一些 Linux 网络工具。所有设备都运行在某个网段上,并连接在网络上;因此,我们必须知道哪些端口是打开的,哪些是关闭的。换句话说,哪些 IP 服务正在运行并可供您的用户使用是服务器网络安全的一部分,它可以是网络访问控制的一部分。让我们看看如何在 Linux 系统上检查网络配置。 | ① | 首先,要获得 Linux 服务器上所有网络接口的列表,使用`netstat –i`。`[root@centos8s1 ~]# netstat -i``Kernel Interface table` | 国际会计师联合会 | 移动式测试装置(Mobile Test Unit) | RX-正常 | 接收错误 | RX-DRP | rx-UFO | tx-好的 | TX-ERR | TX-DRP | TX-OVR(消歧义) | Flg | | ens160 | One thousand five hundred | Five thousand six hundred and one | Zero | Zero | Zero | Two thousand five hundred and eighty-seven | Zero | Zero | Zero | BMRU | | 我会的 | Sixty-five thousand five hundred and thirty-six | Zero | Zero | Zero | Zero | Zero | Zero | Zero | Zero | 最近最少使用算法 | | virbr0 | One thousand five hundred | Zero | Zero | Zero | Zero | Zero | Zero | Zero | Zero | 电池管理单元 | | | ② | 为了练习我们的`netstat`命令,让我们在您的 CentOS 服务器上安装 nginx web 服务服务器并启动 web 服务。启动 Nginx web 服务后,运行`netstat –tulpn : grep :80`命令确认您的 CentOS 服务器正在本地接口的端口 80 上进行监听。对于 CentOS 8.1,安装并启动 nginx web 服务器。`[root@centos8s1 ~]# yum install -y nginx``[root@centos8s1 ~]# whatis nginx``nginx (3pm)          - Perl interface to the nginx HTTP server API``nginx (8)            - "HTTP and reverse proxy server, mail proxy server"``[root@centos8s1 ~]# whereis nginx``nginx: /usr/sbin/nginx /usr/lib64/nginx /etc/nginx /usr/share/nginx /usr/share/man/man3/nginx.3pm.gz /usr/share/man/man8/nginx.8.gz``[root@centos8s1 ~]# systemctl start nginx``[root@centos8s1 ~]# netstat -tulpn | grep :80``tcp     0      0 0.0.0.0:80       0.0.0.0:*        LISTEN      6046/nginx: master``tcp6    0      0 :::80                 :::*        LISTEN      6046/nginx: master`At this point, if you log into your CentOS desktop through VMware’s main console, you will be able to open the Nginx home page in the Firefox web browser using `http://localhost` or `http://192.168.183.130`. See Figure 8-1.![img/492721_1_En_8_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig1_HTML.jpg)图 8-1。CenOS 桌面,打开 http://192.168.183.130 | | ③ | 要永久启用端口 80 上的 HTTP 连接并允许其他机器通过 HTTP 连接,请将 HTTP 服务添加到 CentOS 防火墙的服务列表中。然后验证 HTTP 防火墙服务是否正常运行。要应用更改,您必须使用`firewall-cmd --reload`命令重新加载防火墙服务。下面列出了完整的命令:`[root@centos8s1 ~]# firewall-cmd --permanent --add-service=http``success``[root@centos8s1 ~]# firewall-cmd --permanent --list-all``public``target: default``icmp-block-inversion: no``interfaces:``sources:``services: cockpit dhcpv6-client ftp http ntp ssh tftp``ports: 40000-41000/tcp``protocols:``masquerade: no``forward-ports:``source-ports:``icmp-blocks:``rich rules:``[root@centos8s1 ~]# firewall-cmd --reload``success`我们的网络中没有 DNS 服务器,所以我们必须使用 CentOS 服务器的 IP 地址。我们已经知道我们的服务器 IP 是 192.168.183.130,但是为了验证这个信息,让我们运行下面的命令。或者,您可以使用`ip add`命令浏览信息。`[root@centos8s1 ~]# ip addr show ens160 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//'``192.168.183.130``fe80::f49c:7ff4:fb3f:bf32``[root@centos8s1 ~]# ip add``[...omitted for brevity]``2: ens160: mtu 1500 qdisc fq_codel state UP group default qlen 1000``link/ether 00:0c:29:85:a6:36 brd ff:ff:ff:ff:ff:ff``inet 192.168.183.130/24 brd 192.168.183.255 scope global noprefixroute ens160``valid_lft forever preferred_lft forever``inet6 fe80::f49c:7ff4:fb3f:bf32/64 scope link noprefixroute``valid_lft forever preferred_lft forever``[...omitted for brevity]`At this point, you can open a web browser from your Windows 10 host PC/laptop and open the Nginx home page using `http://192.168.183.130`. Now you have successfully installed Nginx and opened port 80 on your server. See Figure 8-2.![img/492721_1_En_8_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig2_HTML.jpg)图 8-2。Windows 主机,打开 http://192.168.183.130 | | ④ | 要列出您的 Linux 服务器上的所有 TCP 或 UDP 端口,您可以使用`netstat –a`命令;`-a`选项代表`all`。您可以仅为 TCP 端口指定`-t`选项,或者为正在使用的 UDP 端口指定`-u`选项。对于 TCP:`[root@centos8s1 ~]# netstat -at`Active Internet connections (servers and established) | 普罗托呼吸装置 | 接收-Q | 发送队列 | 本地地址 | 国外地址 | 状态 | | 传输控制协议 | Zero | Zero | centos8s1:smux | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 0.0.0.0:主机 | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 0.0.0.0:sunrpc | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 0.0.0.0:http | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | centos8s1:域 | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 0.0.0.0:ssh | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | centos8s1:ipp | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Sixty-four | Centi8s1:Ssh | 192.168.183.1:54572 | 确定的 | | tcp6 | Zero | Zero | [::]:主持人 | [::]:* | 听 | | tcp6 | Zero | Zero | [::sun RPC | [::]:* | 听 | | tcp6 | Zero | Zero | [::]:http | [::]:* | 听 | | tcp6 | Zero | Zero | [::]:ftp | [::]:* | 听 | | tcp6 | Zero | Zero | [::]:ssh | [::]:* | 听 | | tcp6 | Zero | Zero | centos8s1:ipp | [::]:* | 听 | `For UDP``[root@centos8s1 ~]# netstat -au`Active Internet connections (servers and established) | 普罗托呼吸装置 | 接收-Q | 发送队列 | 本地地址 | 国外地址 | 状态 | | 用户数据报协议 | Zero | Zero | 0.0.0.0:48026 | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | centos8s1:58365 | _ 网关:域 | 确定的 | | 用户数据报协议 | Zero | Zero | centos8s1:域 | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | 127.0.0.53:域 | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | 0.0.0.0:引导盘 | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | centos8s1:bootpc | 192.168.183.254:引导区 | 确定的 | | 用户数据报协议 | Zero | Zero | 0.0.0.0:tftp | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | 0.0.0.0:sunrpc | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | 0.0.0.0:ntp | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | 0.0.0:snmp | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | 0.0.0.0:mdns | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | 0.0.0.0:主机 | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | centos8s1:323 | 0.0.0.0:* |   | | udp6 | Zero | Zero | [::]:tftp | [::]:* |   | | udp6 | Zero | Zero | [::sun RPC | [::]:* |   | | udp6 | Zero | Zero | [::]:mdns | [::]:* |   | | udp6 | Zero | Zero | [::]:主持人 | [::]:* |   | | udp6 | Zero | Zero | centos8s1:323 | [::]:* |   | | udp6 | Zero | Zero | [::]:53619 | [::]:* |   | | | ⑤号 | 如果你想只列出监听端口,那么可以使用`netstat –l`。列表太长,所以下面的输出只显示了监听端口信息的顶部,但是也许您可以在 CentOS VM 上运行这个命令并研究返回的结果。`[root@centos8s1 ~]# netstat -l`Active Internet connections (only servers) | 普罗托呼吸装置 | 接收-Q | 发送队列 | 本地地址 | 国外地址 | 状态 | | 传输控制协议 | Zero | Zero | centos8s1:smux | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 0.0.0.0:主机 | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 0.0.0.0:sunrpc | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 0.0.0.0:http | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | centos8s1:域 | 0.0.0.0:* | 听 | `[...omitted for brevity]` | | ⑥ | 像`netstat –a`命令一样,`netstat –l`也可以与`–t`或`–u`选项一起使用,用于仅 TCP 和 UDP 信息。`netstat –lt`只会列出 TCP 监听端口,而`netstat –lu`只会列出 UDP 监听端口。`netstat -lt``netstat -lu`在 Linux concole 上运行前面的命令,看看 CentOS 服务器会输出什么。 | | ① | 如果你想检查哪个进程使用了一个特定的端口,你可以通过键入`netstat –an`结合一个管道(`|`)和`grep ':port_number'`来检查。因此,如果您想检查谁在使用 SSH(端口 22),您可以发出`netstat –an | grep ':22'`命令。`netstat –an | grep ':22' <<< for SSH service``[root@centos8s1 ~]# netstat -an | grep ':22'``tcp        0     0 0.0.0.0:22                0.0.0.0:*                LISTEN``tcp        0     64 192.168.183.130:22       192.168.183.1:54572      ESTABLISHED``tcp6       0     0 :::22``netstat –an | grep ':80'  <<< for http service``[root@centos8s1 ~]# netstat -an | grep ':80'``tcp        0     0 0.0.0.0:80                0.0.0.0:*          LISTEN``tcp6       0     0 :::80                     :::*               LISTEN` | | ⑧ | 要检查哪些 IP 网络服务正在您的服务器上运行,您可以发出`netstat –ap`命令。下面是一个`netstat –ap | grep ssh`命令的例子:`[root@centos8s1 ~]# netstat -ap | grep ssh``tcp   0      0 0.0.0.0:ssh     0.0.0.0:*            LISTEN       1257/sshd``tcp   0     64 centos8s1:ssh   192.168.183.1:54572  ESTABLISHED  1609/sshd: root [pr``tcp6  0     0 [::]:ssh         [::]:*               LISTEN       1257/sshd``unix  2      [ ]         STREAM     CONNECTED     42663    1609/sshd: root [pr``unix  2      [ ]         STREAM     CONNECTED     35797    1609/sshd: root [pr``unix  2      [ ]         STREAM     CONNECTED     43450    2204/sshd: root@pts``unix  2      [ ]         DGRAM                    43437    1609/sshd: root [pr``unix  3      [ ]         STREAM     CONNECTED     43441    1609/sshd: root [pr``unix  3      [ ]         STREAM     CONNECTED     43440    2204/sshd: root@pts``unix  3      [ ]         STREAM     CONNECTED     32383    1257/sshd``unix  2      [ ]         STREAM     CONNECTED     32584    1257/sshd` | | ⑵ | 在输出中显示进程 ID (PID)和程序名称;您可以使用`netstat –pt`命令。它将显示协议、IP 服务和 IP 地址信息供您参考。以下命令显示活动 SSH 连接的`netstat –pt`输出:`[root@centos8s1 ~]# netstat -pt``Active Internet connections (w/o servers)``Proto Recv-Q Send-Q Local Address  Foreign Address    State      PID/Program name``tcp   0   64 centos8s1:ssh   192.168.183.1:54572     ESTABLISHED 1609/sshd: root [pr` | | ⑩ | 最后一组命令由统计命令组成;由于输出很长,您可以从 CentOS VM 控制台运行以下命令。你对`–s`句柄的提示是单词*统计*。请从 CentOS Linux 服务器运行以下命令,按照您自己的速度研究输出:`[root@centos8s1 ~]# netstat –s``[root@centos8s1 ~]# netstat –st``[root@centos8s1 ~]# netstat -su` | ## 安装 TFTP、FTP、SFTP 和 NTP 服务器 这里有一堂针对非 CCNA 读者的快速词汇课: **FTP:** 文件传输协议 SFTP: 安全文件传输协议 **TFTP:** 琐碎文件传输协议 **NTP:** 网络时间协议 在商业领域,PC 最终用户和 IT 工程师都使用其网络中支持的各种网络服务。在众多 IP 服务中,目前网络和系统工程师使用和支持的最流行的网络服务包括 FTP、SFTP、TFTP 和 NTP 服务。每个服务都可以安装在多个分布式服务器上,如果安装和配置受支持,甚至可以安装在网络平台上。但是在实验室中,没有理由将网络服务分布在四个独立的虚拟服务器上来支持四种不同的网络服务。因为我们已经安装了 Nginx 服务,所以在一台服务器上有五个 IP 服务。出于测试目的,您可以将所有服务安装在一台 Linux 服务器上,但是在实际的生产环境中,这些服务可以分布在多台服务器上。 在生产环境中,FTP、SFTP 和 TFTP 是需要大量存储空间的文件服务。大容量存储要求意味着这些服务通常几乎总是安装在企业 Linux 或 Windows 服务器上,而不是路由器和交换机等网络设备上。NTP 是一种时间服务,因此该服务可以在 Cisco 路由器上运行。在 NTP 的情况下,提供时间同步服务,以便服务器和网络设备都可以在标准时间运行。为了方便起见,所有这四种 IP 服务都可以捆绑到一台服务器中,并在我们的实验室中用作 IP 服务服务器。如果您来自一个纯粹的网络背景,您可能没有很多构建和支持 Linux 服务器的企业 IP 服务的经验。更好地理解网络和系统管理技能集不是很好吗?知道的多就是自由。出于实验目的,让我们在 CentOS 8 服务器上安装 FTP、SFTP、TFTP 和 NTP 服务器。请记住,我们将 Ubuntu 服务器保留为 Python + Docker 服务器,供以后的实验使用。 ### FTP 服务器安装 文件传输协议(FTP)是一种安全稳定的网络服务,旨在服务器和客户端之间发送和接收许多文件。换句话说,简单的优点是,与较慢的 TFTP 相比,你可以快速地同时发送和接收文件。默认情况下,FTP 使用 TCP 端口 21,其安全性低于其安全版本 SFTP。有几种开源 FTP 服务器可用于 Linux,例如 PureFTPd、ProFTPD 和 vsftpd。在我们的例子中,我们将安装和使用非常安全的 FTP 守护程序(`vsftpd`),因为它安全、稳定、快速。首先,让我们检查 FTP 服务是否在我们的 CentOS 8 服务器上运行。 CentOS 8 中 FTP 服务器的安装和验证如下: | ① | 在安装 CentOS 8 虚拟机时,我们选择了安装 FTP 服务器,但我们不知道具体安装了哪个软件版本。默认情况下,当您选择从安装介质安装 FTP 服务器时,CentOS 将安装`vsftpd`。运行`vsftpd –version`命令检查 CentOS 服务器上`vsftpd`的当前版本。`As expected, vsftpd version 3.0.3 is available.``[root@centos8s1 ~]# vsftpd -version``vsftpd: version 3.0.3`如果 CentOS 服务器上没有安装 FTP 软件,请使用以下两个命令完成安装:`[root@centos8s1 ~]# sudo yum makecache``[root@centos8s1 ~]# yum install -y vsftpd` | | ② | 通过键入`systemctl status vsftpd`检查`vsftpd`是否正在您的服务器上运行。`[root@centos8s1 ~]# systemctl status vsftpd`●t0]`Loaded: loaded (/usr/lib/systemd/system/vsftpd.service; enabled; vendor preset: disabled)``Active: active (running) since Mon 2021-01-04 19:15:34 AEDT; 1h 43min ago``Process: 1238 ExecStart=/usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf (code=exited, status=0/SUCCESS)``Main PID: 1248 (vsftpd)``Tasks: 1 (limit: 11166)``Memory: 932.0K``CGroup: /system.slice/vsftpd.service``ice248 /usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf``Jan 04 19:15:33 centos8s1 systemd[1]: Starting Vsftpd ftp daemon...``Jan 04 19:15:34 centos8s1 systemd[1]: Started Vsftpd ftp daemon.`使用`Ctrl+C`退出`vsftpd`状态文件。 | | ③ | 您希望`vsftpd`守护进程在操作系统启动时自动启动,因此您必须使用`systemctl enable`命令启用该服务。启用`vsftpd`服务后,使用之前使用的相同状态命令检查运行状态。当活动状态为`active (running)`时,则`vsftpd`服务处于活动状态并正常工作。`[root@centos8s1 ~]# systemctl enable vsftpd --now``[root@centos8s1 ~]# systemctl status vsftpd` | | ④ | 要在实验室中使用 FTP,需要更新几行`vsftpd.conf`文件。在 nano 中打开`vsftpd.conf`文件,检查现有配置,添加几行配置。`[root@centos8s1 ~]# nano /etc/vsftpd/vsftpd.conf`以下配置应该是现成的,但是如果它们不同,请按如下方式配置设置:`anonymous_enable=NO``local_enable=YES``write_enable=YES`要使用`user_list`控制对 FTP 服务器的访问,请在`userlist_enable=YES`后添加以下行:`userlist_file=/etc/vsftpd/user_list``userlist_deny=NO`要授予您的用户对其主目录的可写访问权限,请使用以下命令:`allow_writeable_chroot=YES`另外,`vsftpd`可以使用任何端口范围进行被动 FTP 连接。最佳做法是为此用途指定端口范围。将以下配置附加到`vsftpd.conf`文件的末尾,并保存文件:`pasv_enable=YES``pasv_min_port=40000``pasv_max_port=41000`要更改文件上传和下载的位置,请更新`local_root`配置。在这个例子中,`$USER`将从`/home/pynetauto/ftp`目录获取你的用户 ID 并打开 FTP 会话。`user_sub_token=$USER``local_root=/home/$USER/ftp`检查并更新之前的设置后,在`/etc/vsftpd/vsftpd.conf`文件的末尾添加以下几行:`### ADDED by Admin ###``# Use user_list``userlist_file=/etc/vsftpd/user_list``userlist_deny=NO``# Allow writeable_chroot to the user``allow_writeable_chroot=YES``# Control passive port range to use between 40000-41000``pasv_enable=YES``pasv_min_port=40000``pasv_max_port=41000``# Use ftp directory under /home/user/``user_sub_token=$USER``local_root=/home/$USER/ftp`记住最后一行:`local_root = /home/$USER/ftp`。确保为 FTP 访问创建一个名为`ftp`的本地文件夹。我们将使用标准用户登录 FTP 服务器,在我的例子中,是`/home/pynetauto/ftp`目录。`[pynetauto@centos8s1 ~]$ mkdir ftp` | | ⑤号 | 使用 vi 编辑器更改 FTP 服务器配置文件,以便所有用户都可以访问它。现在将您的用户 ID 添加到`/etc/vsftpd`下的`user_list`文件中。`[root@centos8s1 ~]# vi /etc/vsftpd/user_list``# vsftpd userlist``# If userlist_deny=NO, only allow users in this file``# If userlist_deny=YES (default), never allow users in this file, and``# do not even prompt for a password.``# Note that the default vsftpd pam config also checks /etc/vsftpd/ftpusers``# for users that are denied.``root``bin``[...omitted for brivety]``nobody``pynetauto``~``"/etc/vsftpd/user_list" 21L, 371C` | | ⑥ | 一旦更改保存到文件中,使用`firewall-cmd`命令打开 FTP 和被动端口。要允许防火墙访问 FTP 端口 20 和 21,请运行以下命令:`firewall-cmd --add-service=ftp --permanent``(OR firewall-cmd --permanent --add-port=20-21/tcp)``firewall-cmd --permanent --add-port=40000-41000/tcp``setsebool -P ftpd_full_access on``firewall-cmd --reload` | | ① | 确保发出`systemctl enable vsftpd`使`vsftpd`在系统启动时自动启动。此外,这里有 FTP 故障排除命令供您使用。根据需要使用这些命令来保持服务正常运行。`systemctl enable vsftpd.service``systemctl start vsftpd.service``systemctl restart vsftpd.service``systemctl stop vsftpd.service``systemctl status vsftpd.service`我们在上一节已经学习了`netstat`命令,您可以使用`netstat`命令来检查 FTP 服务是否运行顺畅。`netstat -ap | grep ftp``netstat -tupan | grep 21``netstat -na | grep tcp6` | | ⑧ | 如果 FTP 服务工作正常,您应该打开您最喜欢的 web 浏览器,并使用服务器的`ftp://SERVER_IP_ADDRESS`打开 FTP 页面。如果提示您输入用户 ID 和密码,请输入您的详细信息。见图 8-3 。`ftp://192.168.183.130/`![img/492721_1_En_8_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig3_HTML.jpg)图 8-3。CentOS FTP 服务器,FTP 在 Firefox web 浏览器中打开 | | ⑵ | 您还可以从您的 Windows 主机 PC 或 Linux 桌面下载 FileZilla 客户端,并使用端口 21 连接到您的新 FTP 服务器。URL: [`https://filezilla-project.org/download.php?platform=win64`](https://filezilla-project.org/download.php%253Fplatform%253Dwin64) | Tip 在主机字段输入`ftp://192.168.183.130`。见图 8-4 。 ![img/492721_1_En_8_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig6_HTML.jpg) 图 8-6。 WinSCP,连接到 CentOS FTP 服务器 ![img/492721_1_En_8_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig5_HTML.jpg) 图 8-5。 WinSCP,连接到 CentOS FTP 服务器 | ⑩ | WinSCP 也是另一个在连接到 FTP 服务器进行文件管理时很方便的 Windows 应用。在主机的 Windows 操作系统和 CentOS 8 Linux 服务器之间上传或下载。参见图 8-5 和图 8-6 。您可以从以下 URL 免费下载 WinSCP:URL: [`https://winscp.net/eng/download.php`](https://winscp.net/eng/download.php) | ![img/492721_1_En_8_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig4_HTML.jpg) 图 8-4。 FileZilla 客户端连接到 FTP 服务器 您已经完成了设置和 FTP 服务器登录测试。现在,您可以使用 FTP 服务器在服务器和其他网络设备之间共享各种文件,如 IOS 和配置文件。当然,您也可以使用 FTP 来备份路由器和交换机配置。 ![img/492721_1_En_8_Figc_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Figc_HTML.gif)使用`find`命令确定包含文件或目录名的目录或文件位置。在下面的例子中,我们寻找的是`pub`目录,它通常是`./var/ftp/`目录下的`ftp`默认目录。 `[root @ localhost ~] #` `cd /` `[root @ localhost /] #` `find . -name "pub"` ### 安装 SFTP 服务器 安全文件传输协议(SFTP)是指通过网络安全共享文件的安全 FTP 方法。SFTP 和 FTP 的区别在于通过网络发送的数据是加密的还是未加密的。SFTP 使用 TCP/UDP 端口 22 作为默认端口。在大多数通过 SFTP 协议的文件传输场景中,将使用 TCP 22 端口。应开发人员的要求,UDP 22 包含在 TCP/IP 开发过程中,但没有完全实现。所以,可以肯定地说,SFTP 主要使用 TCP 端口 22。 `vsftpd`被配置为 FTP,但它也是一个 s FTP 服务器。因为我们已经安装了 3.0.3 版本的`vsftpd`,我们也将把它配置为一个 SFTP 服务器,与 FTP 服务器一起运行。 | ① | 首先,通过 SSH 客户端 PuTTY 以 root 用户身份登录 CentOS 8 VM。您不需要为 SFTP 安装新的软件,但是首先,您需要创建一个特定于 SFTP 的用户帐户。使用`adduser`和`passwd`为 SFTP 文件传输创建一个新用户;这里创建的用户名是`sftpuser`。`[root@centos8s1 ~]# adduser sftpuser``[root@centos8s1 ~]# passwd sftpuser``Changing password for user sftpuser.``New password: **********``Retype new password: **********``passwd: all authentication tokens updated successfully.`快速提示:如果您想删除并重新创建一个用户,您可以使用`userdel`命令。要删除用户的主目录和邮件池,添加`–r`。`[root@centos8s1 ~]# userdel -r sftpuser` | | ② | 在`/var/`目录下创建一个目录`sftp`,然后在`/var/sftp/`目录下创建另一个子目录`sftpdata`,你将在那里存储和共享你的文件。这里给出的目录名是`sftpdata`。`[root@centos8s1 ~]# mkdir -p /var/sftp/``[root@centos8s1 ~]# mkdir -p /var/sftp/sftpdata` | | ③ | 更改`sftp`目录的所有权,以便`sftpuser`可以更改`/var/sftp`的内容。让 root 用户成为`/var/sftp/`目录的所有者。将权限更改为 755,以便`sftpuser`可以修改目录和文件。让`sftpuser`用户成为`/var/sftp/sftpdata`目录的所有者。`[root@centos8s1 ~]# chown root:root /var/sftp``[root@centos8s1 ~]# chmod 755 /var/sftp``[root@centos8s1 ~]# chown sftpuser:sftpuser /var/sftp/sftpdata` | | ④ | 为了限制 SSH 对`/var/sftp/sftpdata`目录的访问,您必须修改`/etc/ssh/`目录中的`sshd_config`文件。打开`sshd_config`文件,添加/修改以下配置。将此处指定的内容复制到配置文件的底部并保存。`[root@centos8s1 ~]# vi /etc/ssh/sshd_config``# Add the following to the end of the file.``### ADDED by Admin ###``Match User sftpuser``ForceCommand internal-sftp``PasswordAuthentication yes``ChrootDirectory /var/sftp``PermitTunnel no``AllowAgentForwarding no``AllowTcpForwarding no``X11Forwarding no` | | ⑤号 | 重新启动`sshd`服务以使之前的更改生效。`[root@centos8s1 ~]# systemctl restart sshd` | | ⑥ | 如果您尝试使用`sftpuser`帐户 SSH 进入 CentOS 服务器,连接将被拒绝并断开。尽管 SSH 和 SFTP 使用相同的连接端口,但是`sftpuser`帐户只能用于 SFTP 连接,而不能用于 SSH 连接。试试看。`[root@centos8s1 ~]# ssh sftpuser@192.168.183.130``The authenticity of host '192.168.183.130 (192.168.183.130)' can't be established.``ECDSA key fingerprint is SHA256:b17PV5polHEKIcl+cFUCGA1KHGcl7xJkw/jhTf0kfZY.``Are you sure you want to continue connecting (yes/no/[fingerprint])? yes``Warning: Permanently added '192.168.183.130' (ECDSA) to the list of known hosts.``sftpuser@192.168.183.130's password: ********``This service allows sftp connections only.``Connection to 192.168.183.130 closed.` | | ① | 这一次,尝试使用 sftp 命令连接到 SFTP 服务器。输入登录密码,然后使用 Ctrl+Z 断开与 SFTP 服务器的连接。`[root@centos8s1 ~]# sftp sftpuser@192.168.183.130``sftpuser@192.168.183.130's password: ********``Connected to sftpuser@192.168.183.130.``sftp>`按 Ctrl+Z 断开与 sftp 服务器的连接。`[1]+  Stopped                 sftp sftpuser@192.168.183.130` | | ⑧ | 使用以下命令检查端口 22 和`sshd`是否正常工作(参见图 8-7 ):`[root@centos8s1 ~]# netstat -ap | grep sshd``[root@centos8s1 ~]# netstat -tupan | grep 22``[root@centos8s1 ~]# netstat -na | grep tcp6`![img/492721_1_En_8_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig7_HTML.jpg)图 8-7。CentOS,netstat -tupan | grep 22 示例 | | ⑵ | Finally, verify that your SFTP server is working correctly. You can use Windows applications such as FileZilla or WinSCP with user ID `sftpuser`, your password, and connecting port 22\. See Figure 8-8 and Figure 8-9.![img/492721_1_En_8_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig8_HTML.jpg)图 8-8。FileZilla 客户端连接示例![img/492721_1_En_8_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig9_HTML.jpg)图 8-9。WinSCP,SFTP 连接示例 | ![img/492721_1_En_8_Figd_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Figd_HTML.gif) **如何打印出 Linux 服务器上配置的所有用户名?** 只需运行以下命令: `[root@centos8s1 ~]# awk -F ':' '{print $ 1}' /etc/passwd` `root` `bin` `daemon` `adm` `[... omitted for brevity]` `tcpdump` `pynetauto` `nginx` `sftpuser` `tftpuser` 现在,您的 CentOS 服务器正在运行 SFTP 服务,通过网络实现安全文件共享。只要有可能,SFTP 必须通过 FTP 或 TFTP 用于两个节点(设备)之间的安全文件传输。接下来是 TFTP 的安装及其配置。 ### 安装 TFTP 服务器 普通文件传输协议(TFTP)使用 UDP 69 作为其默认通信端口。因为 TFTP 使用 UDP,所以它的文件传输操作比 FTP 或 SFTP 简单得多。TFTP 是最常用的文件共享方法之一,用于通过内部网络发送和接收 Cisco IOS 或 NX-OS 映像或配置文件。它仍然在许多网络中使用,但是它仍然使用纯文本进行通信,并且通过 UDP 的文件传输并不总是保证完整的文件传输或安全性。 如前所述,在真实的生产环境中,您不会同时在同一台服务器上运行所有的 FTP、SFTP 和 TFTP 服务,但是我们在实验室环境中将所有三台服务器合二为一。在 CentOS 8 上安装 TFTP 类似于安装`vsftpd`。让我们继续安装实验室使用的 TFTP 服务器。重要的是要记住,当你自己建造实验室时,你总是学到更多,带走更多。 | ① | 首先,使用 root 用户帐户登录 CentOS 8.1,并使用 PuTTY 通过 SSH 登录服务器。登录后,运行`systemctl status firewalld`命令检查防火墙状态。`[root@centos8s1 ~]# systemctl status firewalld``●ifirewalld.service - firewalld - dynamic firewall daemon``Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled)``Active: active (running) since Mon 2021-01-04 19:15:30 AEDT; 3h 18min ago``Docs: man:firewalld(1)``Main PID: 1111 (firewalld)``Tasks: 3 (limit: 11166)``Memory: 33.8M``CGroup: /system.slice/firewalld.service``ervicewausr/libexec/platform-python -s /usr/sbin/firewalld --nofork --nopid` | | ② | 使用以下命令更改服务器防火墙设置,使 TFTP 流量能够与您的服务器通信:`[root@centos8s1 ~]# firewall-cmd --permanent --zone=public --add-service=tftp``[root@centos8s1 ~]# firewall-cmd --reload` | | ③ | 使用 yum install 命令安装 tftp-server、xinetd 服务守护程序和 tftp 客户端软件包。运行以下命令集,在 CentOS 服务器上完成安装:`[root@centos8s1 ~]# yum install xinetd tftp-server tftp``[root@centos8s1 ~]# systemctl enable xinetd tftp``[root@centos8s1 ~]# systemctl start xinetd tftp``[root@centos8s1 ~]# systemctl status xinetd``●ixinetd.service - Xinetd A Powerful Replacement For Inetd``Loaded: loaded (/usr/lib/systemd/system/xinetd.service; enabled; vendor preset: enabled)``Active: active (running) since Mon 2021-01-04 19:15:34 AEDT; 3h 20min ago``Docs: man:xinetd``man:xinetd.conf``man:xinetd.log``Process: 1245 ExecStart=/usr/sbin/xinetd -stayalive -pidfile /var/run/xinetd.pid (code=exited, status=0/SUCCESS)``Main PID: 1281 (xinetd)``Tasks: 1 (limit: 11166)``Memory: 1.3M``CGroup: /system.slice/xinetd.service``ice281 /usr/sbin/xinetd -stayalive -pidfile /var/run/xinetd.pid` | | ④ | 现在我们知道 TFTP 服务器服务运行正常,我们想创建一个名为`tftpuser`的用户帐户,分配足够的权限来完成它的工作,即通过 UDP 端口 69 传输文件。此处创建的`tftp`帐户仅供系统使用,因此您不能将其用作用户帐户。`[root@centos8s1 ~]# useradd -s /bin/false -r tftpuser` | | ⑤号 | 在`/var`目录下创建一个名为`tftpdir`的目录。所有未来的 TFTP 文件共享将通过这个目录。`[root@centos8s1 ~]# mkdir /var/tftpdir` | | ⑥ | 更改`tftpdir`目录的权限,以便`tftp`系统帐户拥有该目录的权限。为了允许一些自由,我们将使用`chmod 777`修改目录。`[root@centos8s1 ~]# chown tftpuser:tftpuser /var/tftpdir``[root@centos8s1 ~]# chmod 777 /var/tftpdir` | | ① | 接下来,在`/etc/xinetd.d/`中创建一个名为`tftp`的服务文件,复制以下内容,并保存文件:`[root@centos8s1 ~]# nano /etc/xinetd.d/tftp``service tftp``{``socket_type = dgram``protocol = udp``wait = yes``user = root``server = /usr/sbin/in.tftpd``server_args = -c -s /var/tftpdir -v -v -v -u tftpuser -p``disable = no``per_source = 11``cps = 100 2``flags = IPv4``}` | | ⑧ | 为了让 TFTP 在 SELinux 中很好地工作,我们必须更新我们的`tftp`目录文件`tftpdir`的变更上下文(`chcon`)。`[root@centos8s1 ~]# chcon -t tftpdir_rw_t /var/tftpdir`让我们再次重启`tftp`和`xinetd`服务。`[root@centos8s1 ~]# systemctl restart xinetd tftp` | | ⑵ | 在对 TFTP 服务器执行验证任务之前,让我们检查两个配置,它们对于 CentOS 服务器上的 TFTP 操作非常重要。首先,确保调整了`/etc/selinux/config`配置,其次,检查`setsebool`设置以允许`tftp`写和目录访问。首先,将`SELINUX = enforcing`改为`SELINUX = permissive`后保存文件。见图 8-10 。`[root@centos8s1 ~]# nano /etc/selinux/config`![img/492721_1_En_8_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig10_HTML.jpg)图 8-10。CentOS、/etc/selinux/config 更新其次,我们必须检查 SELinux 的布尔值`tftp_anon_write`和`tftp_home_dir`,这样 SELinux 就允许 TFTP 文件传输。如果`tftp_anon_write`和`tftp_home_dir`都显示为`off`,使用下面的`getsebool`命令检查`tftp`权限。您必须使用后续命令来启用它们。[root@centos8s1 ~]# `getsebool -a | grep tftp``tftp_anon_write --> off``tftp_home_dir --> off`更新`tftp_anon_write`和`tftp_home_dir`的 SELinux 布尔值。`[root@centos8s1 ~]# setsebool -P tftp_anon_write 1``[root@centos8s1 ~]# setsebool -P tftp_home_dir 1``[root@centos8s1 ~]# getsebool -a | grep tftp``tftp_anon_write --> on``tftp_home_dir --> on`最后,在 CentOS 服务器上最后一次重启`xinetd`和`tftp`服务。`[root@centos8s1 ~]# systemctl restart xinetd tftp` | | ⑩ | 在另一台 Linux 机器上,`ubuntu20s1` (192.168.183.129)服务器,使用`apt install –y tftp`命令安装`tftp`客户端,并执行以下验证任务。首先,在`ubuntu20s1`服务器上安装`tftp`客户端。`root@ubuntu20s1:~# apt install tftp`其次,创建`transfer_file01`并添加一些文本,如下所示:`root@ubuntu20s1:~# nano transfer_file01``This is a file transfer test file only.``Please contact the administrator if you have any issues.``root@ubuntu20s1:~# cat transfer_file01``This is a file transfer test file only.``Please contact the administrator if you have any issues.`第三,为了测试 CentOS `tftp`服务器的`tftp`登录,运行`tftp 192.168.183.130`。连接后,使用`put`命令将`transfer_file01`上传到 TFTP 服务器(192.168.183.130)。`root@ubuntu20s1:~# tftp 192.168.183.130``tftp> put transfer_file01``Sent 99 bytes in 0.0 seconds``tftp>`现在回到`centos8s1`服务器,使用`ls /var/tftpdir`命令检查文件是否已经正确上传。`[root@centos8s1 ~]# ls -lh /var/tftpdir``total 4.0K``-rw-rw-r--. 1 tftpuser  tftpuser    97 Jan  4 22:54 transfer_file01`第四,现在反过来,让我们从 TFTP 服务器(`cenos8s1: 192.168.183.130`)下载一个文件名为`transfer_file77`的文件到文件客户端(`ubuntu20s1: 192.168.183.132`)。在 CentOS 服务器上,我们将`transfer_file01`文件复制为`transfer_file77`。`[root@centos8s1 ~]# cp /var/tftpdir/transfer_file01 /var/tftpdir/transfer_file77``[root@centos8s1 ~]# ls /var/tftpdir``transfer_file01  transfer_file77`在 Ubuntu 服务器上,发出`get file_name`命令从 CentOS TFTP 服务器下载`transfer_file05`文件。`root@ubuntu20s1:~# tftp 192.168.183.130``tftp> get transfer_file77``Received 99 bytes in 0.0 seconds``tftp> ^Z` `# Use Ctrl+Z to exit tftp mode``[1]+  Stopped                 tftp 192.168.183.130``root@ubuntu20s1:~# ls transfer*``transfer_file01  transfer_file77` | 您已经在 CentOS 8 服务器上成功安装了 TFTP 软件,并验证了服务器和客户端之间的 TFTP 操作。到目前为止,FTP、SFTP 和 TFTP 都已在 CentOS 服务器上启动并运行,接下来,您将在 CentOS 上快速安装时间服务(NTP 服务器)软件。当我们在实验室或学习或工作中测试各种场景时,这些服务器会派上用场。 ![img/492721_1_En_8_Fige_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fige_HTML.gif)您可以使用`net-tool`来检查 TFTP 是否正常工作。如果您的 CentOS VM 不识别`netstat`命令,您可以使用`yum install -y net-tool`来安装它,并运行以下一组命令来检查与 TFTP 相关的端口操作: `netstat -na | grep udp6` `netstat –lu` `netstat –ap | grep tftp` `netstat -tupan` `netstat –tupan | grep 69` ### 安装 NTP 服务器 网络时间协议(NTP)用于同步服务器、防火墙、路由器和交换机等企业设备的时间。NTP 服务使用 UDP 端口 123 和 IP 网络服务,该服务必须存在于公司网络中以保持时间正确。运行在同一网段或时区的设备需要有一致的时间,NTP 服务器可以为其他设备提供这项服务。如果网络上成百上千的联网设备都按照它们的硬件时钟时间运行,那么日志和系统文件就不会有一致的时间戳。NTP 服务器充当时间助手,帮助网络设备商定一个单一的参考时间。让我们继续在 CentOS 8 服务器上安装 NTP 服务器,并执行一次快速测试以进行确认。在 CentOS 的上一版本 CentOS 7.5 中,可以安装 NTP,但在 CentOS 8 中,`chrony`守护程序同时提供 NTP 服务器和 NTP 客户端服务。 | ① | 首先,使用 SSH 客户端通过 root 用户凭据登录 CentOS 8 虚拟机。登录后,检查服务器时间和时区是否正确。如果这些信息有任何错误,请按照以下说明正确设置您的时钟和时区:1a .检查服务器上的时间。`[root@centos8s1 ~]# clock``2021-01-05 09:09:56.710758+11:00`1b .检查时区、NTP 状态和其他时间详细信息。[root@centos8s1 ~]# `timedatectl1c`。如果您的时区不正确,请搜索时区列表并找到您的国家/城市。按 Ctrl+Z 退出列表。`[root@centos8s1 ~]# timedatectl list-timezones`1d。现在设置正确的时区。如果你在另一个城市和国家,你的时区会和我的不同,所以根据你的地理位置调整时间。`[root@centos8s1 ~]# timedatectl set-timezone Australia/Sydney`1e。确认您的服务器在正确的当地时间运行。`[root@centos8s1 ~]# clock``2021-01-05 09:16:05.039324+11:00``[root@centos8s1 ~]# date``Tue Jan  5 09:16:13 AEDT 2021` | | ② | 接下来,如果还没有安装`chrony`,使用`dnf`命令`dnf install chrony`安装 NTP 服务器和客户端。`[root@centos8s1 ~]# dnf install chrony` | | ③ | 要使`chronyd`在启动时运行,通过键入`systemctl enable chronyd`启用`chrony`服务。`[root@centos8s1 ~]# systemctl enable chronyd` | | ④ | 打开`/etc/chrony.conf`下的`chrony.conf`文件,允许您的服务器和实验室联网。配置文件允许子网后,`chrony`将作为本地网络的 NTP 服务器。确保更新子网以反映您的虚拟机 NAT 子网。此外,为了使这个时间服务器更可信,我们将改变当地的地层为 3。大多数网络设备和服务器将乐于与等于或小于 stratum 5 的 NTP 服务器同步时间。`[root@centos8s1 ~]# vi /etc/chrony.conf`在 vi 的`--INSERT--`模式下更新以下粗体行,然后用`:wq`命令保存更改:`[...omitted for brevity]``# Allow NTP client access from local network.``#allow 192.168.0.0/16``# ADDED by pynetauto``allow 192.168.183.0/24` `# Add this line to allow local communication``# Serve time even if not synchronized to a time source.``# local stratum 10``# ADDED by pynetauto``local stratum 5` `# Add this line to make this as a stratum 3 server``[...omitted for brevity]``:wq`重启`chronyd`服务以使更改生效。`[root@centos8s1 ~]# systemctl stop chronyd``[root@centos8s1 ~]# systemctl start chronyd``[root@centos8s1 ~]# systemctl status chronyd` | | ⑤号 | 打开防火墙端口以允许网络上的传入 NTP 请求,并重新加载防火墙。`[root@centos8s1 ~]# firewall-cmd --permanent --add-service=ntp``success``[root@centos8s1 ~]# firewall-cmd --reload``Success` | | ⑥ | 最后,为了应用更改,重启`chronyd`守护进程。`[root@centos8s1 ~]# systemctl restart chronyd` | | ① | 转到您的`Ubuntu 20 LTS`服务器,设置时区,安装`ntpdate`,并使用`ntpdate 192.168.183.130`与该服务器同步时间。如果您的 NTP 服务器 IP 不同,请不要忘记更新 IP 地址以反映您的 NTP 服务器 IP。7a .使用`timedatectl`命令更新时区。检查 Ubuntu 服务器上的当前时间和日期。`root@ubuntu20s1:~# timedatectl``Local time: Mon 2021-01-04 22:32:42 UTC``Universal time: Mon 2021-01-04 22:32:42 UTC``RTC time: Mon 2021-01-04 22:32:42``Time zone: Etc/UTC (UTC, +0000)``System clock synchronized: yes``NTP service: active``RTC in local TZ: no`更新时区并再次检查时间和日期。`root@ubuntu20s1:~# timedatectl set-timezone Australia/Sydney``root@ubuntu20s1:~# timedatectl``Local time: Tue 2021-01-05 09:33:29 AEDT``Universal time: Mon 2021-01-04 22:33:29 UTC``RTC time: Mon 2021-01-04 22:33:29``Time zone: Australia/Sydney (AEDT, +1100)``System clock synchronized: yes``NTP service: active``RTC in local TZ: no`7b .检查日期、硬件时钟和时区。`root@ubuntu20s1:~# date``Tue 05 Jan 2021 09:35:23 AM AEDT``root@ubuntu20s1:~# hwclock --show``2021-01-05 09:35:33.118208+11:00``root@ubuntu20s1:~# cat /etc/timezone``Australia/Sydney`7c .在你的 Ubuntu 服务器上安装`ntpdate`。`root@ubuntu20s1:~# apt-get install ntpdate`![img/492721_1_En_8_Figf_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Figf_HTML.gif)注意这里`apt-get`的用法。apt (Advanced Package Tool)合并了`apt-get`和`apt-cache`的功能,但是对于某些包,你仍然需要运行`apt-get`来安装某些包。`apt`命令应该在大多数情况下都能工作,但并不总是如此。谷歌上有大量关于这种差异的文章;你可以在谷歌上快速查找这个区别。URL: [`https://askubuntu.com/questions/445384/what-is-the-difference-between-apt-and-apt-get`](https://askubuntu.com/questions/445384/what-is-the-difference-between-apt-and-apt-get)URL: [`https://phoenixnap.com/kb/apt-vs-apt-get`](https://phoenixnap.com/kb/apt-vs-apt-get)7d。将 Ubuntu 服务器时间与 CentOS 8 NTP 服务器时间同步。如果 NTP 服务运行正常,Ubuntu 服务器将同步其时钟,并开始参考 NTP 服务器的时间请求。`root@ubuntu20s1:~# ntpdate –d 192.168.183.130``5 Jan 10:26:42 ntpdate[5667]: ntpdate 4.2.8p12@1.3728-o (1)``Looking for host 192.168.183.130 and service ntp``host found : 192.168.183.130``transmit(192.168.183.130)``receive(192.168.183.130)``[...omitted for brevity]``server 192.168.183.130, port 123``stratum 5, precision -26, leap 00, trust 000``[...omitted for brevity]``0.000000 0.000000 0.000000 0.000000``delay 0.02592, dispersion 0.00006``offset 5.987462``5 Jan 10:26:58 ntpdate[5667]: step time server 192.168.183.130 offset 5.987462 sec``root@ubuntu20s1:~# date``Tue 05 Jan 2021 10:27:04 AM AEDT`检查 NTP 服务器上的时间(`centos8s1`)。如果时间同步如前所示正确进行,您的 NTP 客户端的时间将与 NTP 服务器的时间同步。`[root@centos8s1 ~]# date``Tue Jan  5 10:27:10 AEDT 2021`或者,您可以使用`ntpdate 192.168.183.130`或`ntpdate –u 192.168.183.130`命令来更新和重新同步时间。 | | ⑧ | If you break this IP services server, now is the perfect time to take a snapshot of your CentOS server to preserve the server’s working state. See Figure 8-11.![img/492721_1_En_8_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig11_HTML.jpg)图 8-11。CentOS,拍摄快照 | 您已经成功安装了 NTP 服务器,这就完成了 NTP、FTP、SFTP 和 TFTP 的安装和验证。这些服务器将使您的实验室场景变得更加有趣,而且,您将能够研究、破坏和重建您的实验室,就像您小时候最喜欢的玩具一样。 ## Linux TCP/IP 故障排除练习 这是本章的最后一个练习。您将学习一些 Linux 网络基本故障排除技巧,以测试 TCP 和 IP 相关的问题。当您遇到服务器网络问题时,您可以自己解决,而不是交给 Linux 管理员。 | ① | 第一个练习是检查您的服务器是否允许 ping (ICMP)。使用`cat /proc/sys/net/ipv4/icmp_echo_ignore_all`,您可以检查您的服务器是否会响应 ICMP 请求。输出为 0 或 1。0 表示 ICMP 已启用并且可以响应;1 表示 ICMP 被禁用,这意味着 ICMP 请求将被忽略。对于 CentOS 服务器:`[root@centos8s1 ~]# cat /proc/sys/net/ipv4/icmp_echo_ignore_all``0`对于 Ubuntu 服务器:`root@ubuntu20s1:~# cat /proc/sys/net/ipv4/icmp_echo_ignore_all``0`出于测试目的,让我们将 Ubuntu 服务器的值更新为 1(从 0 开始),这样 ICMP 响应在 Ubuntu 服务器上被禁用。然后在两台服务器之间 ping。测试之后,确保将这个值更新回 0。`root@ubuntu20s1:~# nano /proc/sys/net/ipv4/icmp_echo_ignore_all``root@ubuntu20s1:~# cat /proc/sys/net/ipv4/icmp_echo_ignore_all``1``[root@centos8s1 ~]# ping 192.168.183.130``root@ubuntu20s1:~# ping 192.168.183.130 -c 4``PING 192.168.183.130 (192.168.183.130) 56(84) bytes of data.``64 bytes from 192.168.183.130: icmp_seq=1 ttl=64 time=0.386 ms``64 bytes from 192.168.183.130: icmp_seq=2 ttl=64 time=0.602 ms``64 bytes from 192.168.183.130: icmp_seq=3 ttl=64 time=0.576 ms``64 bytes from 192.168.183.130: icmp_seq=4 ttl=64 time=0.607 ms``--- 192.168.183.130 ping statistics ---``4 packets transmitted, 4 received, 0% packet loss, time 3053ms``rtt min/avg/max/mdev = 0.386/0.542/0.607/0.091 ms`不出所料,Ubuntu 服务器没有响应,因为来自 CentOS 服务器的 ICMP 请求被忽略。如果从 CentOS 服务器 ping Ubuntu 服务器,会导致 100%的数据包丢失,如下所示:`[root@centos8s1 ~]# ping 192.168.183.132 -c 4``PING 192.168.183.132 (192.168.183.132) 56(84) bytes of data.``--- 192.168.183.132 ping statistics ---``4 packets transmitted, 0 received, 100% packet loss, time 79ms` | | ② | 要从远程机器测试服务器的开放端口,可以使用`telnet` +远程服务器的 IP 地址 TCP 端口号。Telnet 可用于测试简单的网络套接字连接,但仅适用于 TCP 端口(不适用于 UDP 端口)。例如,如果您想要测试从 Ubuntu 服务器到 CentOS FTP/SFTP 服务器的 FTP (21)和 SFTP (22) TCP 连接,您可以执行以下`telnet`测试并确认端到端连接。连接后,要退出,键入`QUIT`并按回车键。2a .下面是 FTP 连接测试:`root@ubuntu20s1:~# telnet 192.168.183.130 21``Trying 192.168.183.130...``Connected to 192.168.183.130.``Escape character is '^]'.``220 (vsFTPd 3.0.3)``QUIT``221 Goodbye.``Connection closed by foreign host.`2b。下面是 SSH/SFTP 连接测试:`root@ubuntu20s1:~# telnet 192.168.183.130 22``Trying 192.168.183.130...``Connected to 192.168.183.130.``Escape character is '^]'.``SSH-2.0-OpenSSH_8.0``QUIT``Invalid SSH identification string.``Connection closed by foreign host.` | | ③ | 要查看一个程序或进程是否正在监听一个端口,准备接受一个包,使用`netstat`命令。`netstat`这里列出了参数(选项),所以混合搭配并尝试在你的 Ubuntu 服务器上运行它们:`t`—显示 TCP 端口`u`—显示 UDP 端口`a`—显示全部`l`—仅显示监听过程`n`—不解析网络 IP 地址名称或端口号`p`—显示监听不同端口的进程名 3a .从你的 Ubuntu 服务器运行`netstat –tulnp`命令。`root@ubuntu20s1:~# netstat -tulnp`Here are the active Internet connections (only servers): | 普罗托呼吸装置 | 接收-Q | 发送队列 | 本地地址 | 国外地址 | 状态 | PID/程序名称 | | 传输控制协议 | Zero | Zero | 127.0.0.1:45715 | 0.0.0.0:* | 听 | 939/容器 d | | 传输控制协议 | Zero | Zero | 127.0.0.53:53 | 0.0.0.0:* | 听 | 843/systemd-解决 | | 传输控制协议 | Zero | Zero | 0.0.0.0:22 | 0.0.0.0:* | 听 | 1015/sshd: /usr/sbi | | 传输控制协议 | Zero | Zero | 127.0.0.1:631 | 0.0.0.0:* | 听 | 3221/cupsd | | tcp6 | Zero | Zero | :::22 | :::* | 听 | 1015/sshd: /usr/sbi | | tcp6 | Zero | Zero | ::1:631 | :::* | 听 | 3221/cupsd | | 用户数据报协议 | Zero | Zero | 127.0.0.53:53 | 0.0.0.0:* |   | 843/systemd-解决 | | 用户数据报协议 | Zero | Zero | 0.0.0.0:5353 | 0.0.0.0:* |   | 859/avahi 守护程式 | | 用户数据报协议 | Zero | Zero | 0.0.0.0:43413 | 0.0.0.0:* |   | 859/avahi 守护程式 | | 用户数据报协议 | Zero | Zero | 0.0.0.0:631 | 0.0.0.0:* |   | 3252/cups-已浏览 | | udp6 | Zero | Zero | :::51143 | :::* |   | 859/avahi 守护程式 | | udp6 | Zero | Zero | :::5353 | :::* |   | 859/avahi 守护程式 | 3b。从你的 Ubuntu 服务器运行`netstat –tuna`命令。`root@ubuntu20s1:~# netstat -tuna`Here are the active Internet connections (servers and established): | 普罗托呼吸装置 | 接收-Q | 发送队列 | 本地地址 | 国外地址 | 状态 | | 传输控制协议 | Zero | Zero | 127.0.0.1:45715 | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 127.0.0.53:53 | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 0.0.0.0:22 | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 127.0.0.1:631 | 0.0.0.0:* | 听 | | 传输控制协议 | Zero | Zero | 192.168.183.132:22 | 192.168.183.1:56036 | 确定的 | | tcp6 | Zero | Zero | :::22 | :::* | 听 | | tcp6 | Zero | Zero | ::1:631 | :::* | 听 | | 用户数据报协议 | Zero | Zero | 127.0.0.53:53 | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | 0.0.0.0:5353 | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | 0.0.0.0:43413 | 0.0.0.0:* |   | | 用户数据报协议 | Zero | Zero | 0.0.0.0:631 | 0.0.0.0:* |   | | udp6 | Zero | Zero | :::51143 | :::* |   | | udp6 | Zero | Zero | :::5353 | :::* |   | | | ④ | 您还可以使用带有参数的`ss`命令来查看监听端口或端口准备情况。要查看一个程序或进程是否正在监听一个端口,准备接受一个包,使用`ss`。`ss`命令可以在 Ubuntu 和 CentOS 服务器上运行。参见图 8-12 。`t`—显示 TCP 套接字`u`—显示 UDP 套接字`l`—显示监听插座`n`—不解析名称`p`—使用套接字显示过程`[root@centos8s1 ~]# ss –nutlp`运筹学`root@ubuntu20s1:~# ss -nutlp`![img/492721_1_En_8_Fig12_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig12_HTML.jpg)图 8-12。Ubuntu–ss–nutlp 示例 | | ⑤号 | 使用`lsof`列出 Linux 操作系统上开放的端口。要列出进程名和 PID 号以及打开的端口,请键入`lsof –i`。您可以在大多数 Linux 操作系统上使用这个命令。见图 8-13 。`[root@centos8s1 ~]# lsof –i`运筹学`root@ubuntu20s1:~# lsof -i`![img/492721_1_En_8_Fig13_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig13_HTML.jpg)图 8-13。Ubuntu–lsof–I 示例 | | ⑥ | 使用`iptables`命令列出更多的 TCP/IP 通信信息。运行`iptables -xvn –L`显示数据包、接口、源和目的 IP 地址以及正在使用的端口。见图 8-14 。`root@ubuntu20s1:~# iptables -xvn –L`运筹学`[root@centos8s1 ~]# iptables -xvn -L`![img/492721_1_En_8_Fig14_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig14_HTML.jpg)图 8-14。CentOS,iptables-xvn–L 示例 | | ① | 也许您正在从 Windows 客户端连接到您的 FTP 服务器。那么,你如何从你的 Windows 电脑与你的 FTP 服务器建立 FTP 连接呢?您可以使用 Windows 命令行提示符或 Windows PowerShell。`Test-NetConnection`是一个本地 PowerShell 命令,可用于测试简单的连接,如 FTP 端口 21。让我们从您的 Windows 10 主机 PC/笔记本电脑启动 PowerShell,快速测试与 CentOS FTP 服务器的 FTP 连接是否正常工作。参见图 8-15 。PS C:\Users\brendan> `test-NetConnection -ComputerName 192.168.183.130 -Port 21`![img/492721_1_En_8_Fig15_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_8_Fig15_HTML.jpg)图 8-15。Windows 10 主机 PC,PowerShell 测试-网络连接示例 | ## 摘要 在本章中,您学习了更多的 Linux 管理,从如何检查系统信息和如何检查 Linux 系统上的 TCP 和 UDP 端口开始。然后,指导您在 CentOS 服务器上安装 IP 服务,以制作适用于 TFTP、FTP、SFTP 和 NTP 服务的一体化实验室服务器。在本书的后半部分,这个服务器将用于 Python 网络自动化实验室。最后,您学习了一些基本的 TCP/IP 和 Linux 上的连接故障排除,因此您可以在移动中排除连接问题。我希望这是每个人都感兴趣的一章。在下一章,Chapter 9 中,你将处理另一个具有挑战性的主题,正则表达式(又名 regex)。理解 regex 的内部工作方式可以提高您的 Python 编码能力。你必须坚持下去,完成下一章。 ## Storytime 4:成为一名精明的 Linux 管理员,或许是每个 IT 工程师的梦想? IT 行业的每个人都会同意,在 IT 行业工作时,投入时间和资源学习 enterprise Linux 是一项值得的未来投资。成为一名精明的 Linux 管理员是许多 IT 工程师的梦想,如果不是每个工程师的话。至少这是我一段时间以来的梦想。你还记得你的第一次 Linux 体验吗?以下是 Linux 用户在尝试学习 Linux 时可能会经历的一些常见经历,尤其是如果你一直是 Windows 用户的话。 * 大家都说 Ubuntu Desktop 是最适合初学者的 Linux,于是你下载了最新的 Linux `.iso`文件,安装在了自己的 PC 上。没过几天,你就用 Windows 10 把它抹掉了,你又回到了 Windows 操作系统上,就像什么都没有发生过一样。 * 双引导听起来是一件很酷的事情,所以你在同一台主机上安装了 Windows 和 Linux,并在你的笔记本电脑上尝试了多引导选项。几周后,您注意到 Windows 分区管理器上有一个相当大的未使用的分区,心想,真是浪费存储空间。然后你想起来了:这是你的 Linux 操作系统的分区。 * 随着时间的推移,你登录 Windows 的次数越来越多,尽管你的电脑有多引导选项。 * Ubuntu 或 Gnome 桌面体验看起来是一个连接到互联网的不错的用户界面,但是你仍然渴望 Windows 图形用户界面。您已经被 Windows GUI 所吸引。 * 大家都说你要学习 Linux 命令,但是没人告诉你怎么学习 Linux 命令。所有的网站和视频都说你必须背下来,所以你尽最大努力尽可能多地记忆,把每个命令都塞进你小小的大脑,第二天,你甚至记不起前一天学习的一个命令。 * 在你的工作中总会有一些 Linux 专家,他会给你施加压力,确保你再次使用 Linux,即使你已经离开它几年了。永不放弃! 也许你从一开始就喜欢 Linux,并享受学习 Linux 的经历。对于一些人来说,Linux 章节可能是 Python 网络自动化之旅的第一个障碍,但是我们必须继续。根据一些快速的互联网研究,在 IT 行业中仍然只有一小部分真正的 Linux 管理员。在我上一个工作场所,80 名 IT 工程师中有 3 名 Linux 工程师,而在我目前的团队中,只有两名成员(20 名中)精通 Linux。几乎每个会写编程代码的工程师都会同意,只在 Windows 服务器上开发代码并作为服务运行是不可能的。如果你是微软的专家,一年中可能会出现几次蓝屏死机,你对 Windows Server 最好的解决办法就是按下重启键。换句话说,作为一个网络自动化工程师新手,我们必须熟悉 Linux 系统,并开始在 Linux 上使用 Python。毕竟 Python 和 Linux 是齐头并进的。如果你想精通 Python 或任何编码,你必须很好地覆盖 Linux 基础知识。我同意,学习和掌握 Linux 管理是有挑战性的,但是我们总是可以一步一个脚印,随着时间的推移,我们会慢慢地但肯定地到达那里。尽管如此,我还是希望本章和上一章的内容足以让你看完这本书,并保持你对 Linux 的兴趣。自从学习 Python 之后,我用 Linux 改进了我的游戏,Linux 让我重新思考我的职业生涯,走上了一条不同的 IT 职业道路。我强烈建议每个人都尝试一下 Linux。让我们继续努力! # 九、网络自动化的正则表达式 本章致力于学习正则表达式的基础知识。Python 提供了处理和解析文本字符串的本地方法,但是需要许多行代码来定位准确的字符串。尽管 Python 提供了强大的字符串索引方法,但在现实生活中,每次需要处理大型字符串时,您都会面临挑战。我发现很有趣的是,没有其他 Python 网络自动化书籍真正强调用正则表达式处理数据的重要性。许多书掩盖了正则表达式,因为它不是最令人兴奋的讨论话题。我意识到我必须掌握正则表达式才能使用任何编程语言。尽管如此,一开始我也不敢认真对待它。只有当我开始从事真正的项目时,我才意识到我对正则表达式的无知。我明白这一章的内容对许多读者来说消化起来会很痛苦,但你应该坚持下去,集中精力完成这一章;你以后会感谢我的。 ![img/492721_1_En_9_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Figa_HTML.jpg) 正则表达式普遍适用于所有计算机编程语言,并不是 Python 所独有的。一旦你掌握了它们,你就可以把它们应用到所有的编程语言中,比如 Java、C++、JavaScript、Ruby 和 Perl。在 Python 中,`re`模块为您提供了通向正则表达式的通道。通过深入理解正则表达式,并通过`re`模块将这些知识应用到您的 Python 程序中,您将成为一名更好的 Python 程序员。您在这里学到的知识将应用于实验室中的网络自动化应用开发。掌握正则表达式是任何 Python 程序员都不应该跳过的话题。虽然这是本书中较短的章节之一,但你将获得终生受用的宝贵技能。 在学习了基本的 Python 语法和概念之后,你会想要流利地编写 Python 应用。正如我将在整本书中提到的,用 Python 写代码不仅仅是关于语法和概念;与计算中的所有事情一样,编写代码与正确的数据处理息息相关。这些数据可能来自用户和应用变量、读取文件、浏览网页或两台计算机之间的日志。用任何编程语言编写代码几乎总是涉及到处理数据。写了一段时间 Python 代码后,我很快意识到,如果不完全理解正则表达式,就不可能开发任何网络自动化应用。 如果你对 Python 编码很认真,你必须掌握正则表达式和`re`模块的艺术。您将学习 regex 的基础知识,并将正则表达式应用于真实的 Cisco 路由器和交换机文本文件,以便您可以将信息与真实的生产示例相关联。 对于章节要求,您需要访问运行在 VMware Workstation 15 Pro 上的 Windows 10 主机 PC 和 Ubuntu Server 虚拟机,如图 9-1 所示。 ![img/492721_1_En_9_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Fig1_HTML.jpg) 图 9-1。 第九章中要求的装置 ## 为什么是 Regex? 文字。正则表达式( *regex* )是计算中用来处理复杂文本字符串的标准方法,并不是 Python 独有的;在执行数据处理时,它被用在任何地方。乍一看,学习正则表达式可以看作是学习 Python 的一门遥远的学科。尽管如此,当您进入中级水平时,正则表达式的真正威力将会显现出来。在 Python 中,正则表达式也通过一个名为`re`模块的内置 Python 模块来支持。一些读者可能认为学习正则表达式不是学习 Python 的基本部分,但是当您处理实际问题以解决自动化问题时,您将需要从源文件或大字符串中提取特定的字符串(数据)。在编码中,在处理数据以创建变量和运行应用以避免数据处理错误时,每个逗号、句号和空格都很重要。如果有人问我 C、C++、Java、Perl、Ruby、R 和 Python 等编程语言是做什么的,我会毫不犹豫地直接回答是数据处理。用计算机编程语言编写的每一个程序都必须处理数据以服务于它们的目的。换句话说,我们需要一个工具来改进我们的数据处理和加工。正如您在第二章中所了解的,有一些内置的方法可以对字符串中的数据进行切片、连接和索引。当处理一组更广泛的数据时,您将会遇到许多不便,因为您可能不得不提出您的函数来更好地处理大数据。`re`模块来帮忙了,因为它被视为基于 20 世纪 70 年代的旧正则表达式的超级数据处理方法。知道何时何地使用不同的 Python 模块可以节省您重新发明轮子的时间。首先,您需要使用 Python 原生方法来处理数据。其次,使用内置的`re`模块探索数据处理。第三,使用广泛使用的 Python 库(如 NumPy 和 Pandas)探索更高级的数据处理方法。NumPy 库为多维数组提供对象,而 Pandas 提供一个内存中的 2D 表对象,称为 *dataframe* 。每个 Python 程序员都必须很好地学习正则表达式,并利用它们来处理数据以提取特定的字符串。 将 regex 主题作为一个专门章节的另一个原因是基于我最近从事网络自动化的经验。正则表达式在编码中起着至关重要的作用,在各种网络自动化项目中使用 Python 代码时,我怎么强调它们的有用性都不为过。一旦你达到 Python 编码的中级水平,你将不可避免地面对数据处理的挑战,如果你拒绝学习如何使用正则表达式,你就不会进步。在完成本章的所有练习后,你将会很好地掌握发明的最有用的编码工具之一。本章中展示的例子对某些人来说可能不够全面,但涵盖了足够多的场景来帮助你通过 Python 的`re`模块掌握数据处理。每个字符串匹配的要求都是独特的,所以我强烈建议您超越这本书,花时间研究更多的正则表达式教程。有大量免费的在线培训材料来练习适用于您的数据处理情况的用例。 ### 响还是不响 首先,让我们考虑一个简单的例子。您申请了一份具有网络背景的 Python 开发人员的工作。在你通过第一次技术面试后,招聘经理已经给了你一个带回家的任务,分析一串要分析和转换的文本。您的任务是提取交换机名称及其 MAC 地址,以特定格式显示信息。 您获得了以下字符串。您的任务是打印交换机名称,后跟 12 个十六进制字符的 MAC 地址,其中必须保留前六位数字(OUI)以识别制造商的 ID。但是最后六位数字被用`*`屏蔽了,以隐藏真实的 MAC 地址。 下面是一个给定的字符串: ```py sw_mac = '''pynetauto-sw01 84:3d:c6:05:09:11 pynetauto-sw17 80:7f:f8:80:71:1b pynetauto-sw05 f0:62:81:5a:53:cd''' ``` 此外,包括十六进制 a–f 在内的任何字母都必须大写,因此结果必须与此处显示的预期输出相匹配: ```py PYNETAUTO-SW01 843DC6****** PYNETAUTO-SW17 807FF8****** PYNETAUTO-SW05 F06281****** ``` 如果你没有学习过正则表达式,你可以采取以下步骤来编写你的程序: 1. 删除冒号并将字符串转换为大写字母。 2. 在空格处拆分字符串,并将它们添加到列表中。 3. 使用`strip()`方法删除空白,并制作一个更新的列表。 4. 使用`len()`方法根据列表的项目长度来识别名称和 MAC 地址。如果该项有 14 个字符,那么它是一个开关名称;添加到`sw`列表。如果该项有 12 个字符,那么它是一个 MAC 地址;将其添加到 MAC 列表中。MAC 追加时,添加`******`替换 MAC 地址的后半部分。 5. 使用 Python 字典的 zip 方法将两个列表转换成一个字典。 6. 使用`for`循环打印字典的键-值对中的键和值。 在编程语言中,他们说,“剥一只猫的皮有不止一种方法”,但是其中一种方法(通过代码)将在不使用`re`模块的情况下获得想要的结果,看起来类似于清单 9-1 。 ![img/492721_1_En_9_Figb_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Figb_HTML.gif)这里有一个代码和示例字符串下载提示:本书中的所有代码都可以从 GitHub 上的`pynetauto`下载。对于本章中使用的代码,请查找名为`chapter9_codes.zip`的 zip 文件。 URL: [` github。com/pyneto/apess _ pyneto`](https://github.com/pynetauto/apress_pynetauto) ```py >>> sw_mac = '''pynetauto-sw01 84:3d:c6:05:09:11 ... pynetauto-sw17 80:7f:f8:80:71:1b ... pynetauto-sw05 f0:62:81:5a:53:cd''' >>> >>> sw_mac = sw_mac.replace(":", "").upper() # 1 >>> sw_mac '\nPYNETAUTO-SW01 843DC6050911 \nPYNETAUTO-SW17 807FF880711B \nPYNETAUTO-SW05 F062815A53CD\n' >>> list1 = sw_mac.split(" ") # 2 >>> list1 ['\nPYNETAUTO-SW01', '843DC6050911', '\nPYNETAUTO-SW17', '807FF880711B', '\nPYNETAUTO-SW05', 'F062815A53CD\n'] >>> list2 = # 3 >>> for i in list1: # 4 ... list2.append(i.strip()) # 5 ... >>> list2 ['PYNETAUTO-SW01', '843DC6050911', 'PYNETAUTO-SW17', '807FF880711B', 'PYNETAUTO-SW05', 'F062815A53CD'] >>> >>> sw_list = # 6 >>> mac_list = # 7 >>> for i in list2: # 8 ... if len(i) == 14: # 9 ... sw_list.append(i) # 10 ... if len(i) == 12: # 11 ... i = i[:6] + "******" # 12 ... mac_list.append(i) # 13 ... >>> sw_list ['PYNETAUTO-SW01', 'PYNETAUTO-SW17', 'PYNETAUTO-SW05'] >>> mac_list ['843DC6******', '807FF8******', 'F06281******'] >>> sw_mac_dict = dict(zip(sw_list, mac_list)) # 14 >>> for k,v in sw_mac_dict.items(): # 15 ... print(k, v) # 16 lines ... PYNETAUTO-SW01 843DC6****** PYNETAUTO-SW17 807FF8****** PYNETAUTO-SW05 F06281****** Listing 9-1.Native Python Methods ``` 你不需要担心或理解如何阅读前面的代码。重要的是,您可以计算出达到预期结果所需的代码行数。不算换行和数据行,仅使用 Python 方法(不使用任何模块)打印结果就需要 16 行代码。是的,它可以工作,但是需要许多行代码来实现目标。 在你学会了如何使用正则表达式之后,你可以使用 Python 的`re`模块来做同样的任务,数据操作变得轻而易举。代码行数下降,读写更容易。统计清单 9-2 中的代码行数;打印出预期的结果只需要四行代码(不包括换行符和数据线)。因此,当您比较两个清单之间的代码行数时,您可以看到所使用的代码行数显著减少。这是一个简单的例子,但是想想使用大数据的更复杂的任务。编写冗长的定制代码来实现最终目标会花费大量的时间和精力。使用 Python 代码中的正则表达式模块,您的应用代码将变得更简洁、更易于编写、更易于阅读。 ```py >>> sw_mac = '''pynetauto-sw01 84:3d:c6:05:09:11 ... pynetauto-sw17 80:7f:f8:80:71:1b ... pynetauto-sw05 f0:62:81:5a:53:cd''' >>> import re # 1 >>> sw_mac = sw_mac.replace(":", "").upper() # 2 >>> pattern = re.compile("([0-9A-F]{6})" "([0-9A-F]{6})") # 3 >>> print(pattern.sub("\g<1>******", sw_mac)) # 4 PYNETAUTO-SW01 843DC6****** PYNETAUTO-SW17 807FF8****** PYNETAUTO-SW05 F06281****** >>> Listing 9-2.re Example ``` 这个阶段不需要自己写代码。要下载并运行代码,你可以去我的`pynetauto` GitHub 站点,下载清单 9-1 和 9-2 中的代码。 ## 使用 Python 研究正则表达式 用 Python 学习正则表达式有很多方法,这里我们将讨论如何用几种不同的方法学习。尝试一种最适合自己的学习方法,坚持下去,直到完成本章的练习。 ![img/492721_1_En_9_Figc_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Figc_HTML.gif)如果你想跟随清单 9-3 和 9-4 ,你可以从我的 GitHub 站点下载`sh_ver.txt`文件,并在你的 Linux 目录下保存为`sh_ver.txt`。 URL: [` github。com/pyneto/apess _ pyneto`](https://github.com/pynetauto/apress_pynetauto) ### 方法 1:使用记事本++ 你已经在第二章安装了 Notepad++,它是一个很好的工具,可以用来探索正则表达式。在 Notepad++中,有两种学习正则表达式的方法,根据数据或文本的大小,您可以选择任何一种方法。你可以对一个短字符串使用单个 Python 文件,如图 9-2 所示,在 Notepad++中使用 Ctrl+F6 或 Ctrl+F5 执行脚本。 ![img/492721_1_En_9_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Fig2_HTML.jpg) 图 9-2。 使用 Notepad++和一个脚本学习正则表达式 如果文本数据很长或者要读取多个文件,可以使用文件读取方法将文件读入 Python 脚本并运行您编写的 Python 代码。该示例如图 9-3 和 9-4 所示。 ![img/492721_1_En_9_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Fig4_HTML.jpg) 图 9-4。 使用 Notepad++的正则表达式,读取文件方法数据文件 ![img/492721_1_En_9_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Fig3_HTML.jpg) 图 9-3。 使用 Notepad++的正则表达式,读取文件方法 re 脚本 ### 方法 2:使用 Linux Shell 像 Notepad++方法一样,您可以运行一个 Linux VM 服务器,并通过 SSH 连接到服务器来练习 Python 上的正则表达式。如果你想测试一个简单的正则表达式,你可以用 Python 解释器启动一个交互式会话(参见清单 9-3 )。在您键入表达式并按 Enter 键后,它会立即返回结果,或者如果您的表达式不匹配任何字符串,则不返回结果。您也可以在 Windows 10 主机上使用 Python 3 来使用这种方法。与 Notepad++中一样,您也可以先编写一个 Python 代码,然后从终端控制台运行它来运行`re`匹配脚本,如清单 9-4 所示。这种方法听起来可能很难,但是当您练习几个例子时,您会熟悉它,并且在 Python 中使用正则表达式会变得更容易。 ```py pynetauto@ubuntu20s1:~/ex_regex$ pwd /home/pynetauto/ex_regex pynetauto@ubuntu20s1:~/ex_regex$ ls sh_ver.txt pynetauto@ubuntu20s1:~/ex_regex$ nano ex9.4_sh_ver.py pynetauto@ubuntu20s1:~/ex_regex$ cat ex9.4_sh_ver.py import re with open("/home/pynetauto/ex_regex/sh_ver.txt") as f: read_file = f.read() # Only match Cisco router model number from show version output. rt_model = re.findall("[A-Z]{3}\d{4}\w+", read_file) print(rt_model) my_router = rt_model[0] print(my_router) pynetauto@ubuntu20s1:~/ex_regex$ python3 ex9.4_sh_ver.py ['ISR4351/K9'] ISR4351/K9 Listing 9-4.Regular Expression Writing Python Code on Linux ``` ```py pynetauto@ubuntu20s1:~$ pwd /home/pynetauto pynetauto@ubuntu20s1:~$ mkdir ex_regex pynetauto@ubuntu20s1:~$ cd ex_regex pynetauto@ubuntu20s1:~/ex_regex$ nano sh_ver.txt pynetauto@ubuntu20s1:~/ex_regex$ ls sh_ver.txt pynetauto@ubuntu20s1:~/ex_regex pynetauto@ubuntu20s1:~/ex_regex$ 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 re >>> with open("/home/pynetauto/ex_regex/sh_ver.txt") as f: ... read_file = f.read() ... >>> # Only match Cisco router model number from show version output. >>> rt_model = re.findall("[A-Z]{3}\d{4}\w+", read_file) >>> print(rt_model) ['ISR4351/K9'] >>> my_router = rt_model[0] >>> my_router 'ISR4351/K9' Listing 9-3.Regular Expression on Python Interpreter ``` ### 正则表达式细分:[A-Z]{3}\d{4}[/]\w+ | [A-Z]{3} | \d{4} | [/] | \w+ | | --- | --- | --- | --- | | 三个大写字母 | 四位数 | 字符/ | 至少出现一次的任何字符串 | ### 方法 3:使用互联网学习正则表达式 学习正则表达式最有效的方法之一是使用 web 浏览器并查看 Internet 上的内容。有很多网站提供免费的正则表达式练习。一些网站偏向于一种编程语言而不是另一种,但是试着找到中性的编程语言网站并多加练习。一旦建立了对正则表达式的信心,就可以开始使用前面展示的前两个 Python `re`方法之一。在 Python 中练习`re`模块很重要,因为这是你在 Python 中使用正则表达式的方式。比较好的正则表达式练习站点之一是 regex101 ( [`https://regex101.com`](https://regex101.com) )。见图 9-5 。 ![img/492721_1_En_9_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Fig5_HTML.jpg) 图 9-5。 正则表达式练习网站 如果你一直在忙,但仍然想练习正则表达式,你可以在 Android 设备或苹果 iOS 设备上练习。在移动设备上下载 RegexPal(适用于 Android 手机)或 RegEx Lab(适用于 iOS 设备)或类似的应用。这些应用对于随时随地进行简单的正则表达式练习非常有用。参见图 9-6 。 ![img/492721_1_En_9_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Fig6_HTML.jpg) 图 9-6。 Android 正则表达式应用示例 ## 正则表达式操作:基础 正则表达式使用元字符来匹配静态或动态数据字符串。 *meta* 这个词是一个拉丁(希腊语)词,意思是之后的*、*之后的*或者*之后的*。当在编程语言中使用时,元字符从它们的实际表示中具有特殊的含义,因此元字符可以被定义为具有隐藏含义的字符。元字符包括以下内容:* ```py . ^ $ * + ? \ | ( ) [ ] { } ``` 当在正则表达式中使用这些元字符之一时,它具有一些特殊的含义。最后六个字符成对出现,所以在正则表达式中必须成对使用括号。先从最直白的正则表达式开始,慢慢积累知识。此外,如果您想将这些字符表示为单个文字字符,那么元字符可以用一组方括号(`[ ]`)括起来,或者使用反斜杠(`\`)进行转义。但是,要特别注意元字符`^`和`\`;使用方括号方法无法精确匹配这两个字符。见表 9-1 。 表 9-1。 正则表达式元字符 | 元字符 | 术语 | 用\转义 | 使用[ ]进行字面匹配 | | --- | --- | --- | --- | | `.` | 点 | `\.` | [.] | | `^` | 脱字号 | `\^` | [\^] | | `$` | 美元 | `\$` | [$] | | `*` | 星星 | `\*` | [*] | | `+` | 加 | `\+` | [+] | | `?` | 问号 | `\?` | [?] | | `\` | 反斜线符号 | `\\` | [\\] | | `|` | 管 | `\|` | [|] | | `( )` | 圆括号 | `\( \)` | [(] [)] | | `[ ]` | 方括号 | `\[ \]` | [[][]] | | `{ }` | 波形括号 | `\{ \}` | [{] [}] | 为了避开与`^`和`\`的字面匹配方法,您可以在方括号内的`^`和`\`前添加一个反斜杠。见清单 9-5 。 与所有其他章节一样,为了全面理解正则表达式的概念,你需要打开你的 Python 解释器,在每个练习中输入所有用粗体标记的内容。这包括三个大于号(`>>>`)之后的所有内容。 ```py >>> import re >>> expr = " . ^ $ * + ? \ | ( ) [ ] { }" >>> re.search(r'[\^]', expr) >>> re.search(r'[\\]', expr) Listing 9-5.Matching Metacharacter ^ and \ Using Square Brackets ([ ]) ``` 如果在方括号组中使用了`^`或`\`,则需要在元字符前添加转义反斜杠。这是有充分理由的,当用在方括号内时,`^`用来否定它后面的字符。在你的 Python 解释器中输入清单 9-6 并仔细研究它。`[^a]`意味着匹配除*以外的所有字符。在清单 9-6 中,除了字母 *a* 之外,所有字符都匹配。* ```py >>> import re >>> re.findall('[^a]', 'abracadabra') ['b', 'r', 'c', 'd', 'b', 'r'] Listing 9-6.Meaning of [^a] ``` 方括号内的反斜杠`[\]`也有一个小问题,因为正则表达式将反斜杠识别为右方括号`]`的否定字符。所以,我们必须通过添加另一个反斜杠来否定反斜杠。看看清单 9-7 ,其中反斜杠必须用来匹配反斜杠和一个右方括号`]`。 ```py >>> re.search(r'[\\]', 'match \ or ]') >>> re.search(r'[\]]', 'match \ or ]') Listing 9-7.Meaning of [\]] ``` ### 字符类([]) 我们将学习的第一个元字符是字符类元字符。通常在左开方括号(`[`)和右闭方括号(`]`)之间表示一个由字符类组成的正则表达式。字符类[ ]中允许使用所有字符。 例如,`[aei]`的正则表达式将匹配元音字母 *a* 、 *e* 或 *i* 中的任何一个。我们来看看和闹铃相关的词的真实用法:*叮*,*嗡*,*哔*,*铿锵*。 | 正则表达式 | 单词(字符串) | 相配的 | 说明 | | --- | --- | --- | --- | | `[aei]` | `ding` | `ding` | 逐字匹配字符 *i* (区分大小写) | | `buzz` | 嗡嗡声 | 不匹配;*嗡嗡声*不包含 *a* 、 *e* 或 *i* | | `beep` | 哔哔声 | 匹配字符 *e* 两次 | | `clang` | 叮当声 | 逐字匹配字符 *a* | 当连字符(`-`)用在带有字母或数字的方括号内时,它缩写了一个范围的表达式。例如,`[a-z]`采用全小写字母的含义。`[A-Z]`取从 *A* 到 *Z* 所有大写字母的含义。你已经猜到了:要同时用小写和大写表示所有字母,要匹配的正则表达式就变成了`[a-zA-Z]`。如果你想表达一个十六进制数,你将使用`[0-9a-fA-F]`,意思是从 *a* 到 *f* 开始的所有整数和大小写字母。在正则表达式中,大小写很重要。常见的例子`[0-9]`取 0 到 9 的意思,意为`[0 1 2 3 4 5 6 7 8 9]`。 几乎所有的字符都可以用在方括号`[ ]`之间,但是有一个例外,那就是`^`(脱字符)符号(`^`元字符)。当`^`用在方括号内时,所用的表达取与`NOT`相反的意思或意义。比如想用一个表达式排除任何整数,可以用`[⁰-9]`。如果不想匹配任何大写字母,可以使用`[^A-Z]`。 对于常用的正则表达式,如`[0-9]`和`[a-zA-Z]`,有一些特殊的表达式可以节省时间,使正则表达式更加简洁易读。以下列表供您参考: | 正则表达式 | 可互换表达式 | 说明 | | --- | --- | --- | | `\d` | `[0-9]` | 匹配从 0 到 9 的整数。 | | `\D` | `[⁰-9]` | 匹配除整数以外的所有字符。 | | `\s` | `[ \t\n\r\f\v]` | 匹配所有空格。包括一个空格。 | | `\S` | `[^ \t\n\r\f\v]` | 匹配除空白字符以外的所有字符。 | | `\w` | `[a-zA-Z0-9]` | 匹配所有字母数字。 | | `\W` | `[^a-zA-Z0-9]` | 与字母数字不匹配。匹配所有符号,如`% # @`。 | 在编程领域,反斜杠(`\`)通常用于否定反斜杠后面的表达式的含义。例如,`d`是字母表中的字母 *d* ,但如果和反斜杠组合成`\d`,这就呈现出不同的含义。此外,大写字母的特殊表达总是与小写字母表达相反的意思。 ### 点(。):单个字符匹配 正则表达式`.`(点)匹配除了行终止符如`\n`之外的所有字符。有趣的是,正则表达式提供了包含`\n`的方法,我们将在后面的例子中看到,但是通过使用`re.DOTALL`选项,`.`(点)也可以包含换行符`\n`。 让我们考虑下面的正则表达式例子。 | 正则表达式 | 说明 | | --- | --- | | `d.g` | 匹配字母 *d* 和 *g* 之间的任意单个字符 | 任何指定的字符都必须匹配。例如,字母 *d* 必须在第一个位置匹配,字母 *g* 必须在第三个位置匹配。中间字符可以匹配除新字符`\n`之外的任何字符。 | 正则表达式 | 单词(字符串) | 相配的 | 说明 | | --- | --- | --- | --- | | `d.g` | 狗 | 狗 | 匹配所有字符 *d* 、 *o* 和 *g* 。 | | d%g | d%g | 匹配所有字符 *d* 、% sign 和 *g* 。 | | d \ ng | 没有人 | 不匹配, *\n* 被忽略,不匹配。 *d* 和 *g* 匹配,但是 *\n* 被忽略,所以 *d\ng* 不匹配。 | 如果点字符在方括号之间,`[ ]`怎么办? | 正则表达式 | 说明 | | --- | --- | | `d[.]g` | 方括号之间的点字符将其字面意思作为一个点(`.`)。所以,它只会匹配`d.g`,而不会匹配`dog`或`d%g`。 | ### 星号(*):重复 让我们放大这个,看看下面的正则表达式。 | 正则表达式 | 说明 | | --- | --- | | `zo*m` | 如果星号前面的字符出现零次、一次或多次,则匹配。匹配前面的表达式。在这种情况下,字母 *o* 。 | 元字符`*`有重复的意思,它匹配前面的表达式 *o* 零次、一次或几乎无限次。`*`在尝试匹配字符可能出现或可能不出现的不可预测事件时变得方便;因此,它可能匹配,也可能不匹配。 所有的例子都由元字符`*`匹配。 | 正则表达式 | 单词(字符串) | 相配的 | 说明 | | --- | --- | --- | --- | | `zo*m` | 赞比亚 | 赞比亚 | 匹配零次 o*。即使没有 *o* ,`zm`也是旗鼓相当。* | | 播放器 | 播放器 | 匹配 *o* 一次。 | | 嗡嗡声 | 嗡嗡声 | 匹配中间的 *oo* 。 | ### 加号(+):重复 加号(`+`)是与重复相关联的另一个元字符。它与星号有点相似,但又略有不同,因为零次不被视为匹配。使用`+`元字符时,必须至少匹配一个字符。让我们再次使用相同的字符进行解释。 | 正则表达式 | 说明 | | --- | --- | | `zo+m` | 如果字母 *o* 出现一次或多次,则匹配 | +元字符匹配一些单词。 | 正则表达式 | 单词(字符串) | 相配的 | 说明 | | --- | --- | --- | --- | | `zo+m` | 赞比亚 | 没有人 | 中间没有 o,所以不匹配 | | 播放器 | 播放器 | 匹配 *z* 、 *o* 、 *m* | | 嗡嗡声 | 嗡嗡声 | 匹配 *z* 、 *oo* 和 *m* | ### {m,n}:重复 使用元字符`{m, n }`,您可以匹配重复的次数。字母`m`是比赛计数的开始,`n`是比赛计数的结束。比如`o{1, 3}`取匹配字符 *o* 一到三次的意思。再比如`o{3, }`,这意味着前置字符 *o* 的重复至少要匹配三次以上。再比如`o{, 3}`会匹配同一个字符 *o* 多达三次。所以,`{0,}`的正则表达式等价于`+`,`{1,}`等价于`*`。我们先通过看一些例子来学习`{m, n}`。 #### {m} 参考字母或`m`是匹配前置字符所需的精确重复次数。看一个简单的例子和解释。 | 正则表达式 | 说明 | | --- | --- | | `Zo{1}e` | 匹配第一个字母 *Z* ,然后匹配字母 *o* `m`多次(`m` =1),在本例中只匹配一次,然后匹配字母 *e* 。所以,期望的匹配字母是`Zoe`。注意,正则表达式是区分大小写的。 | 让我们看更多的例子来帮助你理解。如果你能在阅读这本书的同时在电脑键盘上输入这些,你会学到更多。 | 正则表达式{m} | 单词(字符串) | 相配的 | 说明 | | --- | --- | --- | --- | | `o{2}` | 面向对象的 | 面向对象的 | 匹配字符 *oo* | | 动物园 | 动物园 | 匹配字符 *oo* | | 繁荣 | 繁荣 | 匹配字符 *oo* | | `zo{2}m` | 赞比亚 | 没有人 | 缺少 *oo* ,所以不匹配 | | 播放器 | 没有人 | 缺少 *oo* ,所以不匹配。 | | 嗡嗡声 | 嗡嗡声 | 匹配字符 *z* 、 *oo* 和 *m* | #### {m,n} 第一个参考数字或`m`是最小重复数,第二个参考数字或`n`是最大重复数。首先,简单解释一下`{m, n}`是如何工作的。 | 正则表达式 | 说明 | | --- | --- | | `o{2, 5}` | 匹配字符 *oo* 、 *ooo* 、*ooo*或*oooo*。匹配出现两到五次的字母 *o* 。 | 现在让我们看一些简单的例子。 | 正则表达式{m,n} | 单词(字符串) | 相配的 | 说明 | | --- | --- | --- | --- | | `zo{1,3}m` | 赞比亚 | 没有人 | 不匹配, *o* 匹配 0 次 | | 嗡嗡声 | 嗡嗡声 | 匹配 *z* 、 *oo* 和 *m* | | 祖姆 | 没有人 | 不匹配,多了一个 o | #### ?(问号:重复) 元字符与`{0, 1}`的含义相同,可以互换使用。再次使用一个简单的例子,这里有更多关于`?`如何在正则表达式中工作的解释。 | 正则表达式 | 说明 | | --- | --- | | `Zoe?` | 匹配*佐伊*或*佐伊*。问号前的字符 *e* 变成可选。所以,连弦都是 Zo。正则表达式仍将匹配该单词。这个可以写成`Zoe{0, 1}`。 | 使用与之前相同的示例来更好地理解`?`。 | 正则表达式 ? | 单词(字符串) | 相配的 | 说明 | | --- | --- | --- | --- | | `zo?m` | 赞比亚 | 赞比亚 | 匹配`z`和`m`。字母 *o* 是可选的,所以不必匹配。 | | 播放器 | 播放器 | 匹配 *z* 、 *o* 、 *m* 。 | | 嗡嗡声 | 没有人 | 不匹配,因为只需要一个 *o* 。 | 如前所示,`*`、`+`和`?`元字符可以用`{m, n}`方法替换,但是使用`*`、`+`和`?`使得正则表达式更容易阅读和理解,所以只要有可能,尽量使用`*`、`+`和`?`而不是`{m, n}`格式。 ## Python 的 re 模块 如前所述,Python 提供了开箱即用的正则表达式特性,它由一个名为`re`(正则表达式)的标准模块支持。当 Python 安装在您的操作系统上时,此模块将作为标准 Python 库的一部分预安装。 为了开始您的 Python `re`模块之旅,值得一提的是,使用`re`模块编写 Python 代码的方式或风格有所不同。您可以选择保持脚本结构简单、标准或结构化。在简单风格中,您可以用一行代码编写正则表达式语句,如表 9-2 中的简单风格示例所示。当您使用标准样式时,您不必使用`re.compiler`语句,但是您仍然可以通过分离字符串和正则表达式语句来添加一些样式。如果您选择在代码中有更多的一致性,您可以使用编译器风格,使用`re.compiler`语句。任何编码风格都可以,但是在代码中使用`re.compiler`语句的优势是显而易见的。它为您的代码提供了更多的控制和结构,因此也增加了样式的一致性。 让我们通过查看每个示例来快速比较问题中的三种风格。所有三个例子将返回相同的结果,但是以两种不同的风格编写。 表 9-2。 Python 中 re 模块的不同使用方法 | 风格 | 例子 | | --- | --- | | 简单的 | `import re``m = re.findall('\dx\d{4}', "Configuration register is 0x2102")``print(m)` | | 标准 | `import re``expr = "Configuration register is 0x2102"``m = re.findall('\dx\d{4}', expr)``print(m)` | | 编译程序 | `import re``expr for expression``expr for expression``p for pattern``p for pattern``m for match``m for match``print(m)` | | 结果 | `['0x2102']` | 在第三种编译器风格中,我们给编译器语句一个单独的变量`p`,它包含我们想要匹配的正则表达式。然后变量`m`使用变量`expr`和`p`执行匹配功能。如果编译后的正则表达式必须使用两次以上,那么使用这种方法比使用更简单的方法更有利。你必须注意 Python 允许你写短代码,但是你失去了结构和风格,而更多的代码行给你更好的控制和结构,但是你必须写更多的代码行来达到同样的结果。在我看来,这里没有正确或错误的答案;这完全取决于你的风格和个人喜好。 ### Python re 字符串方法 我们必须掌握四种基本类型的正则表达式搜索方法。让我们快速浏览一下这四种方法,然后通过在 Python 解释器上做一些练习来回顾每一种方法。参见表 9-3 。 表 9-3。 关于字符串方法 | 方法 | 说明 | | --- | --- | | `re.match()` | 在第一行搜索正则表达式模式并返回 match 对象。(仅搜索第一行。)如果没有找到匹配,则返回`None`。 | | `re.search()` | 搜索正则表达式模式并返回第一个匹配项。与`re.match()`方法不同,该方法将检查所有行。如果没有找到匹配,则返回`None`。 | | `re.findall()` | 搜索正则表达式模式并匹配所有匹配项。与`re.match()`或`re.search()`方法不同,该方法将一个字符串中所有不重叠的模式匹配作为一个字符串列表返回。 | | `re.finditer()` | 搜索正则表达式模式,这个方法返回一个迭代器,在字符串的`re`模式的所有不重叠匹配中产生`MatchObject`实例。 | #### 重新匹配() 清单 9-8 、清单 9-9 和清单 9-10 将返回相同的匹配目标,但写法不同。仔细研究每一行的写法。同样,这里没有正确或错误的答案,只有不同的方法来编写相同的代码以实现相同的目标。 ```py >>> import re >>> expr = '0x2142 Configuration register is 0x2102' >>> p = re.compile(r'\d\w\d{4}') >>> m = p.match(expr) >>> print(m) Listing 9-10.re.match() method 3 ``` ```py >>> import re >>> expr = '0x2142 Configuration register is 0x2102' >>> re.match(r'\d\w\d{4}', expr) Listing 9-9.re.match() method 2 ``` ```py >>> import re >>> re.match(r'\d\w\d{4}', '0x2142 Configuration register is 0x2102') Listing 9-8.re.match() method 1 ``` 让我们再做一个比赛练习。在清单 9-11 中,`match`对象返回包含`match`字符串开始和结束位置的跨度。相比之下,如果清单 9-12 中没有匹配的对象,则返回值为`None`。在 Python 中,`None`的作用与其他语言中的`null`相同,这意味着它不匹配任何东西。 ```py >>> import re >>> p = re.compile('[a-z]+') >>> expr = "5 regular expression" >>> m = p.match(expr) >>> print(m) Result: None Listing 9-12.re.match() ``` ```py >>> import re >>> p = re.compile('[a-z]+') >>> expr = "five regular expression" >>> m = p.match(expr) >>> print(m) Listing 9-11.re.match() ``` 前面的练习对于我们构建 Python 正则表达式脚本流是必不可少的。查看返回的值,最佳实践可能是以下面的格式构建我们的 Python `re`脚本,这样只有在找到匹配时脚本才会继续运行: | 本章正则表达式的推荐脚本流 | | --- | | `import re``p = re.compile("Enter_re_here")``expr = 'string_to_search_here'``m = p.match(expr)``if m:``print('Match found: ', m.group())``else:``print('Match not found')` | ←导入 re 模块←编译一个模式←数据或表达式←使用模式匹配模式←仅在找到匹配时执行 | #### 重新搜索() 如前所示,让我们使用`re.search()`方法来执行匹配函数,在清单 9-13 中,返回的结果与正则表达式匹配字符串并返回匹配对象的`re.match()`相同。 ```py >>> import re >>> p = re.compile('[a-z]+') >>> expr = "five regular expression" >>> m = p.search(expr) >>> print(m) >> import re >>> p = re.compile('[a-z]+') >>> expr = "5 regular expression" >>> m = p.search(expr) >>> print(m) Listing 9-14.re.search() ``` #### re . findall() 这次让我们使用相同的正则表达式流来练习一下`findall()`方法。在 Python 解释器控制台中输入每一行以获得更多练习。与前两种方法不同,`findall()`方法将匹配的对象作为列表返回。在清单 9-15 中,每个单词都以字符串的形式返回到列表中。 ```py >>> import re >>> p = re.compile('[a-z]+') >>> expr = "five regular expression" >>> m = p.findall(expr) >>> print(m) ['five', 'regular', 'expression'] Listing 9-15.re.findall() exercise 1 ``` 在清单 9-16 中,`findall()`匹配所有满足正则表达式条件的字符串,但忽略数字 5。该方法将尝试匹配并以列表格式返回所有匹配的对象。 ```py >>> import re >>> p = re.compile('[a-z]+') >>> expr = "5 regular expression" >>> m = p.findall(expr) >>> print(m) ['regular', 'expression'] Listing 9-16.re.findall() exercise 2 ``` #### re.finditer() 最后一个练习是关于`finditer()`方法的。像以前一样,让我们键入代码,并通过做一些练习来学习(参见清单 9-17 和清单 9-18 )。`finditer()`方法返回与`finditer()`方法相同的结果,但是它返回字符串正则表达式模式的所有不重叠匹配的迭代器。对于文本处理来说,它可能是一个强大的工具,但是`finditer()`方法的用例非常狭窄。 ```py >>> import re >>> p = re.compile('[a-z]+') >>> expr = "5 regular expression" >>> m = p.finditer(expr) >>> print(m) >>> for r in m: print(r) ... Listing 9-18.re.finditer() exercise 2 ``` ```py >>> import re >>> p = re.compile('[a-z]+') >>> expr = "five regular expression" >>> m = p.finditer(expr) >>> print(m) >>> for r in m: ... print(r) ... Listing 9-17.re.finditer() exercise 1 ``` ### 匹配对象方法 前面,我们看到了使用 match 和 search 方法从正则表达式匹配中返回的对象,但是我们仍然对匹配的字符串以及它们是如何工作的有一些疑问。为了更好地理解这些返回对象的属性,在运行 match 和 search 方法之后,我们可以使用 match 对象方法来回答一些问题。首先,让我们快速回顾一下表 9-4 。 表 9-4。 重新匹配对象方法 | 匹配方法 | 说明 | | --- | --- | | `group()` | 返回匹配的字符串 | | `start()` | 返回匹配字符串的起始位置 | | `end()` | 返回匹配字符串的结束位置 | | `span()` | 以元组格式返回匹配字符串的开始和结束位置 | 现在我们通过练习来学习匹配对象方法,从匹配的返回对象中确认返回的对象。在使用匹配方法的第一个练习中,我们可以使用`group`、`start`、`end`和`span`匹配方法分别获得`match`对象的属性。将清单 9-19 中的代码输入解释器,并交互检查结果。 ```py >>> import re >>> p = re.compile('[a-z]+') >>> expr = "automation" >>> m = p.match(expr) >>> print(m) >>> m.group() 'automation' >>> m.start() 0 >>> m.end() 10 >>> m.span() (0, 10 Listing 9-19.re.match() ``` 在清单 9-20 所示的搜索匹配方法中,如果我们想找出`match`对象在内存中的确切位置,可以发出`group`、`start`、`end`、`span`而不在末尾设置圆括号。无论如何,我们应该更关注`match`对象属性的`group()`、`start()`、`end()`、`span()`结果。 ```py >>> import re >>> p = re.compile('[a-z]+') >>> expr = "5\. regular expression" >>> m = p.search(expr) >>> print(m) >>> m.group >>> m.group() 'regular' >>> m.start >>> m.start() 3 >>> m.end >>> m.end() 10 >>> m.span >>> m.span() (3, 10) Listing 9-20.re.search() ``` ## 编译选项 编译正则表达式时,还可以向表达式添加选项。一、快速复习选项。使用这些正则表达式选项时,可以使用完整的描述性选项名称,如`re.DOTALL`、`re.IGNORECASE`、`re.MULTILINE`和`re.VERBOSE`,也可以使用缩写形式,如`re.S`、`re.I`、`re.M`和`re.X`。参见表 9-5 。 表 9-5。 重新编译选项 | 选择 | 缩写 | 说明 | | --- | --- | --- | | `DOTALL` | S | 匹配任何字符,包括换行符`\n`。 | | `IGNORECASE` | 我 | 使 regex 不区分大小写或忽略大小写。所有主要的正则表达式引擎都以区分大小写的方式匹配;I 模式禁用区分大小写。 | | `MULTILINE` | M | `^`和`$`将匹配一行的开始和结束,而不是整个字符串。它使正则表达式引擎能够处理由多行组成的输入字符串。 | | `VERBOSE` | X | 允许使用详细模式。空白被忽略。空格、制表符和回车符与空格、制表符和回车符不匹配。 | 再说一次,没有付出就没有收获,所以让我们将下面的练习输入到 Python 解释器中,并从示例中学习。 ### re。DOTALL (re。s) `DOTALL`选项用于在匹配字符串中包含一个换行符,让我们看看`DOTALL`选项在实际使用中是如何工作的。见清单 9-21 和清单 9-22 , ```py >>> import re >>> expr = 'a\nb' >>> p = re.compile('a.b', re.DOTALL) >>> m = p.match(expr) >>> print(m) ← Matches object Listing 9-22.re.DOTALL() ``` ```py >>> import re >>> expr = 'a\nb' >>> p = re.compile('a.b') >>> m = p.match(expr) >>> print(m) None ← No match found due to newline Listing 9-21.Without re.DOTALL() ``` 在清单 9-21 中,`\n`字面上表示一个换行符,由于这个原因,正则表达式`a.b`不能单独匹配`a\nb`。在清单 9-22 中,当我们使用`re.DOTALL`启用`DOTALL`选项时,我们可以确认甚至`\n`字符也作为字符串的一部分被匹配。在正常情况下,您不会使用`DOTALL`选项来匹配换行符。相反,它将通过忽略换行符来匹配多行中的字符串。 ### re。IGNORECASE(关于。(一) 当`IGNORCASE`选项被启用时,它使正则表达式不区分大小写,因此大写和小写字母都匹配,而不管字母字符的大小写。同样,让我们做一些练习来看看`re.IGNORECASE` ( `re.I`)是如何工作的。参见清单 9-23 。 ```py >>> import re >>> expr1 = 'automation' >>> expr2 = 'Automation' >>> expr3 = 'AUTOMATION' >>> p = re.compile('[a-z]+', re.IGNORECASE) >>> m1 = p.match(expr1) >>> print(m1) >>> m2 = p.match(expr2) >>> print(m2) >>> m3 = p.match(expr3) >>> print(m3) Listing 9-23.re.IGNORECASE() ``` 仅仅一个练习就足以理解`re.IGNORECASE`或`re.I`的用法。在清单 9-23 中,相同的模式被使用了三次,以匹配三个不同表达式(字符串)中的模式。三种大小写类型(全部小写、首字母大写和全部大写表达式)被用作我们的目标字符串。正则表达式将匹配所有三个字符串,因为在正则表达式模式中启用了`re.I`选项。 ### re。多行(重。m) `re.MULTILINE`或`re.M`选项与`^`和`$`正则表达式元字符一起使用。如前所述,`^`元字符用于指示字符串的开始,而`$`用于标记字符串的结束。通俗地说,`^Network`的正则表达式意味着第一个字符串必须以单词 *network* 开头,而`automation$`意味着最后一个字符串必须以单词 automation 结尾。还是那句话,我们边做边学`MULTILINE`。参见清单 9-24 和清单 9-25 。 ```py >>> import re >>> expr = '''Regular Engineers ... Regular Network Engineers ... Regular but not so Regular Engineers''' >>> p = re.compile('^R\w+\S', re.MULTILINE) >>> m = p.findall(expr) >>> print(m) ['Regular', 'Regular', 'Regular'] Listing 9-25.^ and re.MULTILINE() ``` ```py >>> import re >>> expr = '''Regular Engineers ... Regular Network Engineers ... Regular but not so regular Engineers''' >>> p = re.compile('^R\w+\S') >>> m = p.findall(expr) >>> print(m) ['Regular'] Listing 9-24.without re.MULTILINE() ``` 在这两个练习中,数据(表达式)由三行组成,以常用词`Regular`开头。每一行都以同一个单词开始,正则表达式`^R\w+\S`只匹配第一个单词,以大写字母 *R* 开始,后面是字母数字字母到非白色空间(`\S`)。在清单 9-24 中,不带 re。MULTILINE 选项,搜索只匹配单词 Regular 的第一个匹配项。而在清单 9-25 中,用 re。多行选项启用时,搜索匹配单词,正则在多行中的新行的开头。 ### re。详细(回复。十) 尽可能多地打字,尽可能准确地打字。如果你需要交叉检查你的错别字,你可以从我的下载网站下载本章使用的练习。您可以根据需要复制并粘贴练习信息。 URL: [` github。com/pynetato/apess _ pynetato/`](https://github.com/pynetauto/apress_pynetauto/) 与真实的、生产就绪的脚本中使用的正则表达式相比,本章中介绍的大多数正则表达式示例都是基本的。 现在看下面两个例子(清单 9-26 和清单 9-27);然而,它们是返回相同值的相同脚本。对于对正则表达式了解有限的 Python 编码新手来说,第一个例子(`r'1-9(?:,\d{3})*(?:\.\d*[1-9])?|0?\.\d*[1-9]|0'`)中编译后的正则表达式看起来就像胡言乱语一样。编译后的正则表达式似乎有些过于复杂。它用于匹配与日期相关的数字,并有目的地匹配数字或在年份中使用逗号的数字。第一个例子是我们到目前为止所熟悉的,但是第二个例子充分利用了正则表达式`re.VERBOSE`选项来添加注释并解释正则表达式是如何工作的。虽然两者返回相同的结果,但是后者增加了可读性,甚至对于编写这个正则表达式的人来说也是如此。使用`re.VERBOSE`或`re.X`选项,您可以解码并添加可选注释以增加可读性。 ```py import re expr = 'I was born in 2,009 and I am 15 years old. I started my primary school in 2,010' p = re.compile(r""" [1-9] # Match a single digit between 1-9 (?:\d{0,2}) # Match digit equatl to [0-9] between 0 and 2 times (?:,\d{3})* # Match the character ,(Comma) literally, match a digit equal to [0-9] \ exactly 3 times) Zero and unlimited times (?:\.\d*[1-9])? # Match the character. (Dot) literally, match a digit equal to [0-9] \ zero and unlimited times, match a single digit between [1-9]) \ zero and one time. | # OR 0?\.\d*[1-9] # Match 0 zero or one time, match . (Dot) literally, match a digit \ equal to [0-9] zero or unlimited times, and match a digit between [1-9] | # OR 0 # Match one 0 """, re.VERBOSE) m = p.findall(expr) print(m) Result: ['2,009', '15', '2,010'] Listing 9-27.With re.VERBOSE ``` ```py import re expr = 'I was born in 2,009 and I am 15 years old. I started my primary school in 2,010' p = re.compile(r'1-9(?:,\d{3})*(?:\.\d*[1-9])?|0?\.\d*[1-9]|0') m = p.findall(expr) print(m) Listing 9-26.Without re.VERBOSE ``` ## \:令人困惑的反斜杠字符 `\`在 Python 中赋予元字符特殊的含义;它在 Python 字符串和 regex 引擎中都是一个转义字符,您最终会将您的模式传递给它。当在 Python `re`引擎中使用斜杠字符时,这可能会导致一些混乱,即您必须使用多少转义字符来匹配您想要从数据中提取的确切字符串。为了消除混淆,在用 Python 编译正则表达式时,`r`(原始字符串表示法)被附加在正则表达式之前。让我们输入下面的两个例子(清单 9-28 和清单 9-29 ),第一个没有使用原始字符串符号`r`,第二个使用原始字符串符号`r`。在下面的例子中,我们试图匹配字符串`\scored`。 ```py >>> import re >>> expr = 'Our team \scored three goals\\' >>> p1 = re.compile('\scored') >>> p2 = re.compile('\\scored') >>> p3 = re.compile('\\\scored') >>> p4 = re.compile('\\\\scored') >>> p5 = re.compile('\\\\\scored') >>> print(p1.findall(expr)) [] >>> print(p2.findall(expr)) [] >>> print(p3.findall(expr)) ['\\scored'] >>> print(p4.findall(expr)) ['\\scored'] >>> print(p5.findall(expr)) [] Listing 9-28.Backslashes Without Raw String Notation ``` 在清单 9-29 中,编译后的带有三个反斜杠和四个反斜杠的正则表达式匹配并返回相同的结果。这可能会令人困惑,因为您可能不确定是使用三个还是四个反斜杠来匹配单词和字面反斜杠字符`\scored`。 ```py >>> import re >>> expr = 'Our team \scored three goals\\' >>> p1 = re.compile(r'\scored') >>> p2 = re.compile(r'\\scored') >>> p3 = re.compile(r'\\\scored') >>> p4 = re.compile(r'\\\\scored') >>> print(p1.findall(expr)) [] >>> print(p2.findall(expr)) ['\\scored'] >>> print(p3.findall(expr)) [] >>> print(p4.findall(expr)) [] Listing 9-29.Backslash with Raw String Notation ``` 在清单 9-30 中,我们使用了原始字符串符号`r`,很明显,结果表明您必须使用两个反斜杠来匹配目标字符串`\scored`。在原始字符串符号中使用一个、三个和四个反斜杠将与目标字符串不匹配。 ```py >>> import re >>> expr = 'Our team \scored three goals\\' >>> p2 = re.compile(r'\\scored') >>> m = p2.findall(expr) >>> print(m) ['\\scored'] >>> n = m[0] >>> n '\\scored' >>> for x in n: ... print(x, end=") ... \scored Listing 9-30.Backslash with Raw String Notation ``` 在清单 9-30 中,使用了原始字符串匹配方法,在本例中,原始字符串匹配方法匹配两个(反斜杠)并返回匹配的字符串,在列表中得分。若要转换列表中的项目,可以使用索引方法。 ## 正则表达式:一点修改加更多 在本节中,让我们快速回顾一下到目前为止我们所学的内容,并涵盖更多元字符和正则表达式方法,如分组、前瞻搜索等。 ### 更多元字符 到目前为止,我们已经介绍了最常用的元字符,但是我们还必须介绍一些更常见的元字符。我们在这里讨论的元字符的特征与本章前面讨论的有些不同。我们已经学习了元字符,如`+`、`*`、`[ ]`和`{ }`。当这些元字符在匹配字符串中改变位置时,它们将只搜索字符串一次。现在,让我们通过引入一些新的元字符来扩展我们的元字符词汇表,然后我们将看看分组、前视和后视示例。 #### OR 运算符(|) 在正则表达式中,`|`(管道)元字符与`or`的含义相同。`a|b`的正则表达式与`[ab]`的含义相似,但它们的操作方式不同。`|`和`[ ]`都是 OR 运算符,它们会尝试匹配字符串中的指定字符,但匹配和返回的结果略有不同。让我们看看下面的例子,准确理解`|`和`[ ]`的真正区别。参见清单 9-31 和清单 9-32 。 ```py >>> re.findall('a(b|c)', 'a, ab, ac, abc, acb, ad') ['b', 'c', 'b', 'c'] Listing 9-32.a(b|c) ``` ```py >>> import re >>> re.findall('a[bc]', 'a, ab, ac, abc, acb, ad') ['ab', 'ac', 'ab', 'ac'] Listing 9-31.a[bc] ``` 在清单 9-31 中,您已经使用了`[ ]`正则表达式来匹配`b`或`c`;正则表达式匹配的返回值也包含前导的`a`。但是在清单 9-32 中,你用`|`替换了`[ ]`,返回的值只有`b`或`c`的匹配值,没有前导`a`。所以,`[ ]`和`|`的区别在于它匹配前导字符的方式,但是一个返回前导字符,一个不返回。 现在,看一下清单 9-33 和清单 9-34 ,这用一个正则表达式的范围选项来说明,更明显的是,一个打印前导字符,一个不打印。 ```py >>> re.findall('3(a|b|c|d|e|f)', '3, 3a, 3c, 3f, 3g') ['a', 'c', 'f'] Listing 9-34.3(a|b|c|d|e|f) ``` ```py >>> re.findall('3[a-f]', '3, 3a, 3c, 3f, 3g') ['3a', '3c', '3f'] Listing 9-33.3[a-f] ``` 清单 9-35 和清单 9-36 是带有*苹果*和*树莓*字样的`|`(或)的简单例子。在清单 9-36 中,re.findall 匹配方法匹配两个单词, *rasberry* 和 *apple* 。匹配的单词作为列表返回。 ```py >>> print(re.findall('apple|raspberry', 'raspberry and apple pie')) ['rasberry', 'apple'] Listing 9-36.apple|rasberry ``` ```py >>> re.match('apple|raspberry', 'raspberry pie') Listing 9-35.apple|rasberry ``` #### ^和$(主播) `^`(插入符号)将从字符串的第一个字符开始查找匹配。当您将它与`re.MULTILINE`或`re.M`选项一起使用时,您可以在数据或字符串的每个换行符处启用正则表达式。让我们使用 Python 解释器快速做一些练习。列表 9-37 和列表 9-38 。 ```py >>> re.findall('^Start', 'Start to finish') ['Start'] Listing 9-37.^Start ``` 在清单 9-38 中,`^`指示正则表达式`^Start`匹配字符串开头的单词 *start* 。 ```py >>> re.findall('finish$', 'Start to finish') ['finish'] Listing 9-38.finish$ ``` 在清单 9-38 中,`$`指示正则表达式匹配字符串末尾的单词 *finish* 。 在清单 9-39 中,结合`^`和`$`,正则表达式匹配以 *S* 开始并以 *sh* 结束的精确字符串。 ```py >>> re.findall('^S.+sh$', 'Start to finish\nSpecial fish\nSuper fresh', re.MULTILINE) ['Start to finish', 'Special fish', 'Super fresh'] Listing 9-41.^S.+sh$' and re.M ``` ```py >>> re.findall('^S.+sh$', 'Start to finish') ['Start to finish'] Listing 9-39.^S.+sh$ ``` 在清单 9-40 中,使用`re.M`模块,您已经匹配了以 *S* 开始并以 *sh* 结束的三行字符串。 现在让我们通过另一个练习来看一个简化的实际例子。代码可以从我的 GitHub 网站下载,这个练习的文件名是`5.7.1.2_5.py`([`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto))。见清单 9-41 。 ```py >>> import re >>> expr = '''SYDCBDPIT-ST01#sh ip int brief ... Interface IP-Address OK? Method Status Protocol ... Vlan1 unassigned YES NVRAM up up ... Vlan50 10.50.50.11 YES NVRAM up up ... FastEthernet0 unassigned YES NVRAM down down ... GigabitEthernet1/0/1 unassigned YES unset down down ... GigabitEthernet1/0/2 unassigned YES unset up up ... GigabitEthernet1/0/3 unassigned YES unset up up ... ''' >>> p = re.compile('^Gig.+down$', re.MULTILINE) >>> m = p.findall(expr) >>> print(m) ['GigabitEthernet1/0/1 unassigned YES unset down down'] Listing 9-41.^Gig.+up$ and re.M ``` 您可以很容易地将它应用到真实的场景中,从您的网络设备`show`命令中提取一些接口信息。在清单 9-41 中,我们使用思科交换机的`show ip interface`简短命令的前几行来演示这一点。使用正则表达式`^Gig.+up$`,我们可以快速匹配具有关闭状态的接口。出于演示的目的,这个例子过于简单了。尽管如此,在您必须管理成百上千个 switchport 接口的真实环境中,您需要正则表达式的能力来处理收集的数据。 #### \A 和\Z 在单行字符串匹配中,`^`和`\A`的工作方式相同,但是在尝试匹配多行字符串时,行为会有所不同(参见清单 9-42 和清单 9-43 )。同样适用于`$`和`\Z`;尝试匹配多行字符串时,匹配行为会发生变化(参见清单 9-44 )。当`re.M`或`re.MULTILINE`选项被启用时,^可以在字符串的开头和每个换行符之后匹配,但是`\A`只在字符串的开头匹配(参见清单 9-45 )。另外,`$`可以在字符串末尾和每个换行符之前匹配(参见清单 9-46 ),但是`\Z`只在字符串末尾匹配(参见清单 9-47 )。 ```py >>> re.findall('S.+sh\Z', 'Start to finish\nSuper special fish\nSuper fresh fish\nSuper smelly fish', re.M) ['Super smelly fish'] Listing 9-47.S.+sh\Z with re.MULTILINE ``` ```py >> re.findall('S.+sh$', 'Start to finish\nSuper special fish\nSuper fresh fish\nSuper smelly fish', re.M) ['Start to finish', 'Super special fish', 'Super fresh fish', 'Super smelly fish'] Listing 9-46.S.+sh$ with re.MULTILINE ``` ```py >>> re.findall('\AS.+sh', 'Start to finish\nSuper special fish\nSuper fresh fish\nSuper smelly fish', re.M) ['Start to finish'] Listing 9-45.\AS.+sh with re.MULTILINE ``` ```py >>> re.findall('^S.+sh', 'Start to finish\nSuper special fish\nSuper fresh fish\nSuper smelly fish', re.M) ['Start to finish', 'Super special fish', 'Super fresh fish', 'Super smelly fish'] Listing 9-44.^S.+sh with re.MULTILINE ``` ```py >>> re.findall('\AS.+sh', 'Start to finish') ['Start to finish'] Listing 9-43.\AS.+sh ``` ```py >>> re.findall('^S.+sh', 'Start to finish') ['Start to finish'] Listing 9-42.^S.+sh ``` 以下练习演示了混合使用`^`或`\A`和`$`或`\Z`如何影响匹配的返回结果。我们通过用`\n`分隔每一行来人为地添加新行,以创建新行效果。拿出您的例子并测试这些元字符来扩展您的理解。参见清单 9-48 ,清单 9-49 ,清单 9-50 ,清单 9-51 。 ```py >>> re.findall('\AS.+sh\Z', 'Start to finish\nSuper special fish\nSuper fresh fish\nSuper smelly fish', re.M) [] Listing 9-51.\AS.+sh\Z with re.M ``` ```py >>> re.findall('^S.+sh\Z', 'Start to finish\nSuper special fish\nSuper fresh fish\nSuper smelly fish', re.M) ['Super smelly fish'] Listing 9-50.^S.+sh\Z with re.M ``` ```py >>> re.findall('\AS.+sh$', 'Start to finish\nSuper special fish\nSuper fresh fish\nSuper smelly fish', re.M) ['Start to finish'] Listing 9-49.\AS.+sh$ with re.M ``` ```py >>> re.findall('^S.+sh$', 'Start to finish\nSuper special fish\nSuper fresh fish\nSuper smelly fish', re.M) ['Start to finish', 'Super special fish', 'Super fresh fish', 'Super smelly fish'] Listing 9-48.^S.+sh$ with re.M ``` #### \b 和\B 在正则表达式中,`\b`代表单词边界。在一个句子中,首词和下一个词之间的空白由通常的词边界表示。 | 单词边界 | 描述 | | --- | --- | | `\b` | 代表像`^`(类似于`$`、`^`)这样的主播匹配位置,其中一边是字符(像`\w`),另一边不是字符;例如,它可能是字符串的开头或空格字符。 | | `\B` | 它伴随着它的否定,`\B`。这将匹配所有与`\b`不匹配的位置,如果我们想要找到一个由单词字符包围的搜索模式,那么就可能出现这种情况。 | 根据正则表达式字面规则,`\b`代表退格;因此,当您在正则表达式中使用它时,您总是必须使用原始字符串符号`r`来指定这不是退格字符。 在清单 9-52 中,您试图匹配表达式中的单词 computers。在编译的模式中,我们使用前导的`\b`和结尾的`\b`作为我们试图匹配前导空格和结尾空格的单词,所以这个单词匹配并返回结果。 ```py >>> import re >>> expr = "Small computers include smartphones." >>> p = re.compile(r'\bcomputers\b') >>> m = p.search(expr) >>> print(m) Listing 9-52.\b(word)\b matched ``` 在清单 9-53 中,单词*微型计算机*有单词*计算机*但是以*微型*开始,所以空格不能匹配单词*微型*后面的单词计算机。结果果然是`None`。 ```py >>> import re >>> expr = "Microcomputers include smartphones." >>> p = re.compile(r'\bcomputers\b') >>> m = p.search(expr) >>> print(m) None Listing 9-53.\b(word)\b not match ``` 在清单 9-54 中,第一个`\b`在我们的正则表达式编译器中被删除,现在匹配目标词 *computers* 被匹配并返回预期结果。 ```py >>> import re >>> expr = "Microcomputers include smartphones." >>> p = re.compile(r'computers\b') >>> m = p.search(expr) >>> print(m) Listing 9-54.(word)\b matched ``` `\B`与`\b`意思相反,因此可以用来实现`\b`的反转。在清单 9-55 中,您尝试匹配单词 *computer* 而不在末尾添加复数 *s* 。因此,使用开头的`\B`和结尾的`\B`来精确匹配单词。 ```py >>> import re >>> expr = "Microcomputers include smartphones." >>> p = re.compile(r'\Bcomputer\B') >>> m = p.search(expr) >>> print(m) Listing 9-55.\B(word)\B matched ``` #### 分组 假设您有一个由路由器上摆动链路的 up 和 down 状态组成的字符串,并且您想要执行匹配来搜索连续的 up 状态。搜索并匹配连续上升状态后,可以使用`group()`选项打印输出(列表 9-56 )。要在正则表达式中编译一个组,必须使用`( )`。这是一个不切实际的例子,只是为了向您介绍正则表达式分组的概念。下一个例子将会在真实的环境中进行。 ```py >>> import re >>> expr = "downupupupdowndownupdowndown" >>> p = re.compile("(up)+") >>> m = p.search(expr) >>> print(m) >>> print(m.group(0)) upupup Listing 9-56.Grouping exercise 1 ``` 现在让我们考虑一个技术援助中心的国家名称和美国的全限定号码。首先,我们要编译一个正则表达式来匹配整个字符串,以国家名称开始,然后是技术助理中心的电话号码。参见清单 9-57 。 ```py >>> import re >>> expr = "United States 1 408 526 1234" >>> p = re.compile(r"\w+\s\w+\s\d\s\d{3}\s\d{3}\s\d+") >>> m = p.search(expr) >>> print(m) Listing 9-57.Grouping exercise 2 ``` 在清单 9-57 中,您混合使用了字母数字速记字符`\w`、空格速记字符`\s`和重复数字速记字符`\d``{n}`来匹配所有字符。如果我们在这里停止字符串匹配,这个例子中捕获的数据就没有得到很好的利用。通过使用分组,我们可以从匹配的字符串中提取特定的信息。假设我们只想提取国家名称(参见清单 9-58 )。 ```py >>> import re >>> expr = "United States 1 408 526 1234" >>> p = re.compile(r"(\w+\s\w+)\s\d?\s\d{3}\s\d{3}\s\d+") >>> m = p.search(expr) >>> print(m) >>> country = m.group(1) >>> country 'United States' Listing 9-58.Grouping exercise 3 ``` 在清单 9-58 中,我们可以使用`group(1)`方法从整个匹配的字符串中分离出信息,现在我们可以在脚本中将这些数据作为变量或数据使用。现在让我们快速回顾分组方法索引,以理解正则表达式分组是如何编号的。见表 9-6 。 表 9-6。 分组方法索引含义 | 分组指数 | 说明 | | --- | --- | | `group(0)` | 完整匹配字符串 | | `group(1)` | 匹配的第一组 | | `group(2)` | 匹配的第二组 | | `group(3)` | 匹配的第三组 | | `group(n)` | 匹配第 n 个 | 使用实例可以更好地解释分组方法索引,如清单 9-59 所示。 ```py >>> import re >>> expr = "United States 1 408 526 1234" >>> p = re.compile(r"(\w+\s\w+)\s(\d?\s\d{3}\s\d{3}\s\d+)") >>> m = p.search(expr) >>> print(m) >>> phone_number = m.group(2) >>> phone_number '1 408 526 1234' Listing 9-59.Grouping Method Indexing exercise 1 ``` 在清单 9-60 中,`group(1)`是`(\w+\s\w+)`,与清单 9-59 中的`group(1)`相同,`(\d?\s\d{3}\s\d{3}\s\d+)`变成了`group(2)`,捕获电话号码。参见图 9-7 。 ![img/492721_1_En_9_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Fig7_HTML.jpg) 图 9-7。 正则表达式示例中的组索引 ```py >>> import re >>> expr = "United States 1 408 526 1234" >>> p = re.compile(r"(\w+\s\w+)\s((\d?)\s(\d{3})\s(\d{3}\s\d+))") >>> m = p.search(expr) >> m.group(0) 'United States 1 408 526 1234' >>> m.group(1) 'United States' >>> m.group(2) '1 408 526 1234' >>> m.group(3) '1' >>> m.group(4) '408' >>> m.group(5) '526 1234' Listing 9-60.Grouping Method Indexing exercise 2 ``` 在清单 9-61 中,这些组被细分成更小的组,现在我们可以将国家代码 1、区号 408 和本地号码 526 1234 彼此分开。想象一下,在真实的网络自动化场景中,我们可以用正则表达式分组做所有可能的事情。想象一下,您可以利用从实际生产路由器和交换机中捕获的数据做些什么,并根据收集和分析的数据控制您的设备。 ```py >>> import re >>> expr = "Did you know that that 'that', that that person used in that sentence, is wrong." >>> p = re.compile(r'(\bthat)\s+\1') >>> m = p.search(expr) >>> print(m) >>> m = p.search(expr).group() >>> print(m) that that Listing 9-61.Referencing Grouped String ``` 使用组索引的另一个优点是,您可以使用类似于`+\1`的简写来重新引用第一个组。在`(\bthat)\s+\1`的正则表达式中,`+`有匹配与前一组相同的字符串的意思,`\1`有重新引用组号 1 的意思。如果有第二组并且你想引用它,可以使用`\2`。 #### 正则表达式命名组 让我们假设我们正在进行一个项目,编写一个正则表达式来处理数据,并充分利用`re`分组方法。这将使您的正则表达式极难解码,并在试图理解正则表达式试图匹配的内容时带来一些混乱。在网络中,我们有访问控制列表(ACL)和命名访问控制列表(NACL);类似地,正则表达式还提供了命名组,以便为组附加一个名称,从而简化数据操作。当正则表达式中有大量组时,命名组就变得很方便。命名组是为了在高级搜索和替换中查找和替换时更加方便。 我们必须使用从路由器的`show version`命令中提取的一行,它包含路由器名称和正常运行时间。我们希望使用正则表达式组来提取路由器名称、设备运行的精确年数、周数、天数、小时数和分钟数。 ```py expr = "SYD-GW1 uptime is 1 year, 9 weeks, 2 days, 5 hours, 26 minutes" ``` 为了将它们按组进行匹配,一个可行的分组示例将类似于正则表达式。尽管如此,由于存在多个分组,并且您正在阅读其他人的代码,您可能会对仅仅一行正则表达式感到不知所措。 ```py (\w+[-]\w+)\s.+((\d+\sy\w+),\s(\d+\sw\w+),\s(\d+\sd\w+),\s(\d+\sh\w+),\s(\d+\sm\w+)) ``` 仔细研究正则表达式 10 分钟后,你终于得出结论,字符串就是这样匹配分组的。不幸的是,这么多组,你很容易迷失在翻译中。参见图 9-8 。 ![img/492721_1_En_9_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Fig8_HTML.jpg) 图 9-8。 正则表达式多编号组示例 现在,让我们做这个练习来学习这个正则表达式在真正的 Python 中是如何工作的。同样,您将遵循推荐的正则表达式流程,编译您的正则表达式并将其应用于字符串。如您所料,您可以将路由器名称 SYD-GW1 划分为组 1,通过打印组 2 来打印整个正常运行时间,然后在组 2 中嵌入更小的组,如组 3(年)、4(周)、5(天)、6(小时)和 7(分钟)。如果你还没有感到困惑,你真的已经步入正轨,并且在这个阶段做得很好。参见清单 9-62 。 ```py >>> import re >>> expr = "SYD-GW1 uptime is 1 year, 9 weeks, 2 days, 5 hours, 26 minutes" >>> p = re.compile(r'(\w+[-]\w+)\s.+((\d+\sy\w+),\s(\d+\sw\w+),\s(\d+\sd\w+),\s(\d+\sh\w+),\s(\d+\sm\w+))') >>> m = p.search(expr) >>> print(m.group(0)) SYD-GW1 uptime is 1 year, 9 weeks, 2 days, 5 hours, 26 minutes >>> print(m.group(1)) SYD-GW1 >>> print(m.group(2)) 1 year, 9 weeks, 2 days, 5 hours, 26 minutes >>> print(m.group(3)) 1 year [… omitted for brevity] >>> print(m.group(7)) 26 minutes Listing 9-62.Multiple Numbered Groups exercise ``` 为了在正则表达式中如此复杂地使用分组方法并增加灵活性,正则表达式有一个方便的命名组。您可以简单地在组的开头添加`?P`,并以您指定的组名来引用该组。如果我们必须给前面的例子起一个组名,它看起来将类似于清单 9-63 。给一个组起一个名字会给它所匹配的数据赋予更多的含义,而且您不必花太多时间来解码正则表达式。 ```py >>> import re >>> expr = "SYD-GW1 uptime is 1 year, 9 weeks, 2 days, 5 hours, 26 minutes" >>> p_named = re.compile(r'(?P\w+[-]\w+)\s.+(?P(?P\d+\sy\w+),\s(?P\d+\sw\w+),\s(?P\d+\sd\w+),\s(?P\d+\sh\w+),\s(?P\d+\sm\w+))') >>> m = p_named.search(expr) >>> print(m.group("minutes")) 26 minutes [… omitted for brevity] >>> print(m.group("uptime")) 1 year, 9 weeks, 2 days, 5 hours, 26 minutes >>> print(m.group("hostname")) SYD-GW1 Listing 9-63.Named Group exercise ``` 当您根据字符串数据分析上述分组时,分组将如图 9-9 所示。 ![img/492721_1_En_9_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_9_Fig9_HTML.jpg) 图 9-9。 正则表达式命名组示例 如您所见,这些名称与您试图匹配的信息相关,并且来自多个分组的混淆非常少。你应该把宝贵的工作时间花在更关键的问题上,或者提供良好的客户支持体验,而不是试图解码其他人神秘的正则表达式。 ### 前瞻和后视断言 在进入这一部分之前,我必须先告诉你。如果您是第一次学习正则表达式,您可能无法立即理解 lookaheads 和 lookbehinds。为了减轻痛苦,我使用了练习和例子,但是在完全掌握这个概念之前,您可能需要多次回到这个主题。 ### 向前看、向后看和非捕捉组 Lookaaheads 和 lookbehinds 被称为 *lookarounds* ,经常让 regex 新手感到困惑。乍一看,lookaheads 和 lookbehinds 看起来非常混乱,因为它们都以`?`开头,区别来自第二个和第三个元字符的使用。但是一旦掌握得好,它们就是缩短我们正则表达式的便利工具。你不需要马上理解这一部分;你可以慢慢学习周围的环境。此外,请将此部分作为将来参考的参考点。在环视中,有四个环视和一个非捕捉组,它们都以`?`标记开始。 在动手之前,作为一个快速提醒,表 9-7 展示了快速浏览和简单的例子。 表 9-7。 “向前看”、“向后看”和“非捕捉”组 | 环顾四周 | 名字 | 线 | 例子 | 说明 | | --- | --- | --- | --- | --- | | `?=` | 展望 | `abc` | `a(?=b)` | 正则表达式必须匹配的资产 | | `?!` | 消极前瞻 | `abc` | `a(?!b)` | 断言不可能匹配正则表达式 | | `?<=` | 向后看 | `abc` | `(?<=a)b` | 正则表达式必须匹配的资产 | | `? | 练习# | 练习 | 说明 | | --- | --- | --- | | one | `>>>` `print(re.search('a(?=b)', 'abc'))````>>>` `print(re.search('a(?=b)', 'xbc'))``None` | 匹配`a`,因为下一个字母是`b`。没有匹配,因为第一个字母不是字母`b`前面的`a`。 | | Two | `>>>` `print(re.search('a(?!b)', 'abc'))``None``>>>` `print(re.search('a(?!b)', 'acd'))``` | 没有匹配,因为下一个字母是`b`。匹配`a`,因为下一个字母不是`b`(它是`c`)。 | | three | `>>>` `print(re.search('(?<=a)b', 'abc'))````>>>` `print(re.search('(?<=a)b', 'xbc'))``None` | 匹配`b`,因为前一个字母是`a`。没有匹配,因为前一个字母不是`a`。 | | four | `>>> print(re.search('(?>>` `print(re.search('(?` | 不匹配,因为前一个字母是`a`。匹配`b`,因为前一个字母不是`a`。 | | five | `>>>` `print(re.search('a(?:b)', 'abc'))````>>>` `print(re.search('a(?=b)', 'abc'))``` | 匹配`a`和`b`,因为`b`后面跟着`a`。两个`ab`都匹配。匹配`a`,因为下一个字母是`b`。(这个和练习 1 一样。) | | six | `>>>` `import re``>>>` `expr = "+1 408 526 1234"``>>>` `p = re.compile(r"((?:(\+1)[ -])?\(?(\d{3})\)?[ -]?\d{3}[ -]?\d{4})")``>>>` `m = p.search(expr)``>>>` `print(m)````>>>` `print(m.group(2))``+1` | `?:`表示非捕获组,没有`?:`,+1 和尾随空格作为一组被捕获。使用`?:`,正则表达式不会像其他方式一样通过括号内的匹配来分组(通常,括号创建一个组)。换句话说,当您使用`?:`时,该组被匹配,但不会被捕获用于反向引用,这就是为什么它被称为*非捕获*组。 | 请访问 RexEgg regex 站点,获取更多的深度分析和正则表达式示例。这是我最喜欢的学习正则表达式的网站之一。 网址:[www . rexeg . com/regex-look arounds . html](http://www.rexegg.com/regex-lookarounds.html) ### 多练习环视 你需要大量的练习来理解这个话题。这一次,让我们使用字符串`'abba'`来使练习更加精彩,并使用完整的编译方法来构建我们的学习。 ```py expr = 'abba' ``` 请打开 Python 解释器,并在三个大于号旁边输入以粗体标记的文本。通读这一部分不会帮助你记住信息,所以你必须做所有的练习,从 1 到 10。 | 练习# | 锻炼 | 正则表达式 | 说明 | | --- | --- | --- | --- | | **1** | `>>>` `import re``>>>` `expr = 'abba'``>>>` `p = re.compile('a(?=b)')``>>>` `m = p.search(expr)``>>>` `print(m)``` | `a(?=b)` | 找到后面有`b`的`a`。 | | **2** | `>>>` `import re``>>>` `expr = 'abba'``>>>` `p = re.compile('b(?=b)')``>>>` `m = p.search(expr)``>>>` `print(m)``` | `b(?=b)` | 找到第一个`b`,它后面有`b`。 | | **3** | `>>>` `import re``>>>` `expr = 'abba'``>>>` `p = re.compile('a(?!b)')``>>>` `m = p.search(expr)``>>>` `print(m)``` | `a(?!b)` | 找到第二个`a`,它后面没有`b`。 | | **4** | `>>>` `import re``>>>` `expr = 'abba'``>>>` `p = re.compile('b(?!b)')``>>>` `m = p.search(expr)``>>>` `print(m)``` | `b(?!b)` | 找到第二个`b`,它后面没有`b`。 | | **5** | `>>>` `import re``>>>` `expr = 'abba'``>>>` `p = re.compile('(?<=a)b')``>>>` `print(m)``` | `(?<=a)b` | 找到第一个`b`,它前面有`a`。 | | **6** | `>>>` `import re``>>>` `expr = 'abba'``>>>` `p = re.compile('(?<=b)b')``>>>` `m = p.search(expr)``>>>` `print(m)``` | `(?<=b)b` | 找到第二个`b`,它前面有`b`。 | | **7** | `>>>` `import re``>>>` `expr = 'abba'``>>>` `p = re.compile('(?>>` `m = p.search(expr)``>>>` `print(m)``` | `(?>>` `import re``>>>` `expr = 'abba'``>>>` `p = re.compile('(?>>` `m = p.search(expr)``>>>` `print(m)``` | `(?>>` `import re``>>>` `expr = 'abc'``>>>` `p1 = re.compile('a(?:b)')``>>>` `m1 = p1.search(expr)``>>>` `print(m1)``` | `a(?:b)` |   | | **10** | `>>>` `p2 = re.compile('a(?=b)')``>>>` `m2 = p2.search(expr)``>>>` `print(m2)``` | `a(?=b)` |   | ### 环视应用示例 对于当前和未来学习 Python 编程语言的工程师来说,有很多内容需要涉及。直到你在工作中开始真正的项目,你可能看不到正则表达式的真正价值和力量。到目前为止,已经通过练习向您介绍了一些基本的正则表达式,现在您正在逐步提高正则表达式的技能。然而,除非你每天都在工作中生活和呼吸 regex,否则要达到顶级水平需要很长时间。但是,你仍然可以从这一章学习基础知识,并通过在互联网和其他书籍上找到的其他资料进行补充,以达到中级和高级水平。 让我们用思科 TAC 的网址 [`http://www.cisco.com/techsupport`](http://www.cisco.com/techsupport) 来练习一下我们所学的内容。参见清单 9-64 。 ```py >>> import re >>> m = (re.search(r"\w{4,5}(?=:)", "http://www.cisco.com/techsupport")) >>> print(m.group()) http Listing 9-64.Only Use Lookahed Method to Print http ``` 在清单 9-64 中,正则表达式`\w{4,5}(?=:)`匹配前面出现在冒号(:)前面的字母数字 4 到 5 次。 ```py >>> import re >>> m = (re.search(r"(?<=\/)\w.+[.]\w+[.]\w+(?=/)", "http://www.cisco.com/techsupport")) >>> print(m.group()) www.cisco.com Listing 9-65.Only Use Lookahead and Lookbehind Method to Print www.​cisco.​com ``` 为了只匹配 [`www.cisco.com`](http://www.cisco.com) ,我们使用了一种后视方法(`?<=`),它以正斜杠`/`开始,然后匹配`www`格式。然后我们使用了一个以`/`结尾的前瞻(`?=`)。使用 lookrounds,您可以更快、更精确地匹配字符串。 以下表达式可以匹配任何具有`filename.file_extension`格式的文件名,例如`vlan.dat`、`sw1.bak`、`config.text`,甚至可以匹配 Cisco 交换机 IOS 名称,例如`2960x-universalk9-mz.152-2.E6.bin`。在下面的例子中,我们将比较否定的前瞻方法和`^`的否定方法。 ```py .*[.].*$ ``` 在 expr(表达式或字符串)中,要特别注意在文件名之间使用了`\n`来在单个字符串中创建换行符效果。这是为了简化我们的练习并节省页面。每个练习的后面都有解释。 在清单 9-66 中,您将使用正则表达式`.*[.].*$`来匹配 expr 中的所有文件。 ```py >>> import re >>> expr = "vlan.dat\nsw1.bak\nconfig.text\npynetauto.dat\nsw1_old.bak\n2960x-universalk9-mz.152-2.E6.bin" >>> m = re.findall(".*[.].*$", expr, re.M) >>> m ['vlan.dat', 'sw1.bak', 'config.text', 'pynetauto.dat', 'sw1_old.bak', '2960x-universalk9-mz.152-2.E6.bin'] Listing 9-66.Match All File Types Using Regular Expression - .*[.].*$ ``` 在清单 9-67 中,`^`(脱字符)方法用于否定任何以扩展名`.dat`结尾的文件,并返回列表中所有匹配的文件名。要特别注意的是,我们正在使用本章前面所学的`re.findall`和`re.M` ( `MULTILINE`)方法。 在清单 9-68 中,否定前瞻示例返回与清单 9-67 相同的结果。提取相同数据的方法不止一种。 ```py >>> import re >>> expr = "vlan.dat\nsw1.bak\nconfig.text\npynetauto.dat\nsw1_old.bak\n2960x-universalk9-mz.152-2.E6.bin" >>> m = re.findall(r".*..*$", expr, re.M) >>> m ['sw1.bak', 'config.text', 'sw1_old.bak', '2960x-universalk9-mz.152-2.E6.bin'] Listing 9-68.Filter files with file extensions not starting with the letter d (using lookaround method) ``` ```py >>> import re >>> expr = "vlan.dat\nsw1.bak\nconfig.text\npynetauto.dat\nsw1_old.bak\n2960x-universalk9-mz.152-2.E6.bin" >>> m = re.findall(r".*[.][^d].*$", expr, re.M) >>> m ['sw1.bak', 'config.text', 'sw1_old.bak', '2960x-universalk9-mz.152-2.E6.bin'] Listing 9-67.Filter files with file extensions not starting with the letter d (Not using lookaround method) ``` 在清单 9-69 和清单 9-6687 中,您已经使用两种否定方法否定了任何以`.dat`或`.bak`结尾的文件名。尽管如此,如果文件扩展名以相同的字母开头或者扩展名的长度开始变化,后一种方法还是有优势的。 ```py >>> import re >>> expr = "vlan.dat\nsw1.bak\nconfig.text\npynetauto.dat\nsw1_old.bak\n2960x-universalk9-mz.152-2.E6.bin" > m = re.findall(r".*..*$", expr, re.M) >>> m ['config.text', '2960x-universalk9-mz.152-2.E6.bin'] Listing 9-70.Filter Any Files Ending with dat and bak Using Lookaround Method ``` ```py >>> import re >>> expr = "vlan.dat\nsw1.bak\nconfig.text\npynetauto.dat\nsw1_old.bak\n2960x-universalk9-mz.152-2.E6.bin" >>> m = re.findall(r".*[.][^d|^b].*$", expr, re.M) >>> m ['config.text', '2960x-universalk9-mz.152-2.E6.bin'] Listing 9-69.Filter Any Files Ending with .dat and .bak Without Using Lookaround Method ``` 让我们在清单 9-71 中检查一下。 ```py >>> import re >>> expr = "file1.bak\nfile2.dat\nfile3.bakup\nfile4.data" >>> m = re.findall(r".*[.][^d|^b].*$", expr, re.M) >>> m [] Listing 9-71.Filter Any Files Ending with dat or bak Using ^ Negation Method ``` 在清单 9-71 和清单 9-72 中,返回的结果是不同的,因为清单 9-71 中使用的正则表达式过滤或否定了所有文件,但在清单 9-72 中,只过滤了以`.dat`或`.bak`结尾的文件。如果我们试图编写一个没有清单 9-72 中的负 lookahead 方法的工作正则表达式,那么这个正则表达式将会冗长而晦涩。在清单 9-72 中,使用负的 lookahead 方法,您可以简单地在正则表达式中添加带有|(或)符号的确切扩展名。 ```py >>> import re >>> expr = "file1.bak\nfile2.dat\nfile3.bakup\nfile4.data" >>> m = re.findall(r".*..*$", expr, re.M) >>> m ['file3.bakup', 'file4.data'] Listing 9-72.Filter Any Files Ending with dat or bak Using Negative Lookahead Method ``` ## 子方法:替换字符串 在正则表达式中使用`sub`方法,可以改变或交换匹配的字符串。让我们做第一个练习,并检查结果。 ### 使用 sub 替换字符串 像往常一样,让我们先从一个练习开始,然后回顾你从练习中学到了什么。参见清单 9-73 。 ```py >>> import re >>> p = re.compile('HP|Juniper|Arista') >>> p.sub('Cisco', 'Juniper router, HP switch, Arista AP and Palo Alto firewall') 'Cisco router, Cisco switch, Cisco AP and Palo Alto firewall' Listing 9-73.Use sub to substitute multiple matching words ``` 在清单 9-73 中,使用`sub`方法,将“Juniper 路由器、HP 交换机、Arista AP 和 Palo Alto 防火墙”更改为“Cisco 路由器、Cisco 交换机、Cisco AP 和 Palo Alto 防火墙”因为 Palo Alto 不在编译的正则表达式中,所以只有 Juniper 和 HP 被替换为 Cisco。 按照清单 9-74 进行操作,如果我们只想控制更换的数量呢? ```py >>> p.sub('Cisco', 'Juniper router, HP switch, Arista AP and Palo Alto firewall', count=1) 'Cisco router, HP switch, Arista AP and Palo Alto firewall' Listing 9-74.Use sub to substitute a matching word only once ``` 您可以使用 count 参数控制替换。在前面的例子中,使用了 count 1,所以在清单 9-74 中,只有 Juniper 被替换为 Cisco,而不是 HP 或 Arista。 如果我们想找出一个长字符串中替换的个数呢? ```py >>> import re >>> expr = '''Juniper router, HP switch, Palo Alto firewall, Juniper router, HP switch, Palo Alto firewall, Juniper router, HP switch, Palo Alto firewall, Juniper router, HP switch, Palo Alto firewall, Juniper router, HP switch, Palo Alto firewall, Juniper router, HP switch, Palo Alto firewall, and Arista router''' >>> p = re.compile('HP|Juniper|Arista') >>> p.subn('Cisco', expr) ('Cisco router, Cisco switch, Palo Alto firewall, Cisco router, Cisco switch, Palo Alto firewall, Cisco router, Cisco switch, Palo Alto firewall, Cisco router, Cisco switch, Palo Alto firewall, Cisco router, Cisco switch, Palo Alto firewall, Cisco router, Cisco switch, Palo Alto firewall and Cisco router', 13) Listing 9-75.Use subn to count the number of replacements ``` 你可以用`subn`的方法来替换字符串,找出被替换的字符串的个数。在清单 9-75 中,13 个字符串被替换为单词*思科*。 ### 使用 sub 和\g 交换位置 在正则表达式中使用`sub`方法时,可以将它与相对组引用(`\g`)结合使用。假设我们有一个字符串`"Model Number : WS-C3650-48PD"`,我们想要交换`'WS-C3650-48PD`和`'Model Number'`的位置。所以,看起来是这样的: | **来自** |   | **至** | | --- | --- | --- | | **型号:WS-C3650-48PD** | →中 | **WS-C3650-48PD:型号** | 在 Python 解释器上,输入每个练习中的代码。您将很快了解这是如何工作的。 在清单 9-76 中,您已经将匹配正则表达式编译成三组,并使用`sub`方法,在匹配每组后简单地反转组序列。这个组仍然在同一个地方,但是组 1 和组 3 交换了位置。 ```py >>> import re >>> expr = "Model Number : WS-C3650-48PD" >>> p = re.compile(r"(\w+\s\w+)(\s[:]\s)(\w+[-]\w+[-]\w+)") ← (r"(grp1)(grp2)(grp3)") >>> m = p.sub("\g<3>\g<2>\g<1>", expr) >>> print(m) WS-C3650-48PD : Model Number Listing 9-x >>> p = re.compile(r"(?P\w+\s\w+)(\s[:]\s)(?P(\w+[-]\w+[-]\w+))") >>> m = p.sub("\g\g<2>\g", expr) >>> print(m) WS-C3650-48PD : Model Number Listing 9-76.Use sub and grouping to swap positions ``` 在清单 9-77 中,你也可以应用命名分组,给每个组取有意义的名字,实现我们的目标。请注意,该组没有像`(?P\s[:]\s)`这样的名称,以证明您可以混合搭配命名组和编号组。 ```py >>> p = re.compile(r"(?P\w+\s\w+)\s[:]\s(?P(\w+[-]\w+[-]\w+))") >>> m = p.sub("\g : \g", expr) >>> print(m) WS-C3650-48PD : Model Number Listing 9-77.Use sub and named group method to swap positions ``` 在清单 9-77 中,您只使用了两个命名组,并且省略了逗号组,但是当您使用`sub`方法进行匹配时,您在表达式中添加了(`:`)来返回期望的结果。 ### 在子方法中插入函数 在网络操作中,我们使用二进制、十进制和十六进制值。我们使用十六进制值的两个例子是 MAC 地址和 IPv6 地址。这在 IPv6 寻址方案中变得非常有用;它有助于理解如何从十六进制计算到二进制和十进制,或者反过来。 在下面的练习中,我们将使用`sub`方法练习函数。 在列表 9-78 中,您使用了标准的 Python 方法将十进制随机 IP 地址转换为二进制数。使用列表 9-79 中的`sub`命令也可以得到相同的转换结果。 ```py >>> ip = '172.168.123.245' >>> def dec2bin(match): ... value = int(match.group()) ... return bin(value) ... >>> p = re.compile(r'\d+') >>> p.sub(dec2bin, ip) '0b10101100.0b10101000.0b1111011.0b11110101' Listing 9-79.Decimal to Binary Using the sub Method ``` ```py >>> ip = '172.168.123.245' >>> print ('.'.join([bin(int(x)+256)[3:] for x in ip.split('.')])) 10101100.10101000.01111011.11110101 Listing 9-78.Decimal to Binary Using the join Method ``` 使用`sub`方法时,编译后的返回结果以`0b`开头,表示这是一个二进制数。在清单 9-80 中,我们创建了一个名为`dec2bin`的函数,并在`sub`方法中使用它将十进制 IP 地址转换成二进制数。 ```py >>> ip = "00001010.11010110.10001011.10111101" >>> ip1 = ip.replace(".", "") >>> ip1 '00001010110101101000101110111101' >>> def bin2dec(): ... return ".".join(map(str, int(ip1, 2).to_bytes(4, "big"))) ... >>> bin2dec() '10.214.139.189' Listing 9-80.Binary to Decimal Using Join Method ``` 在清单 9-81 中,我们选择了一个随机的二进制 IP 地址,并使用 Python `join`方法演示了二进制到十进制数的转换。将二进制数转换成小数后,我们知道我们要查找的 IP 地址是 10.214.139.189。 ```py >>> mac = "84:3d:c6:f5:c9:ba" >>> mac1 = mac.replace(":", "") >>> mac1 '843dc6f5c9ba' >>> i = int(mac1, 16) >>> str(i) '145400865868218' Listing 9-81.Hexadecimal to Decimal Numbers ``` 在清单 9-82 中,我们捕获了一个交换机的 MAC 地址,并想将这个十六进制数转换成十进制数。使用基本的 Python 方法,我们可以很容易地将十六进制数转换成十进制数。这是一个很好的例子,说明 Python 如何高效地将数字从一种形式转换成另一种形式。 ```py >>> def hexrepl(match): ... value = int(match.group()) ... return hex(value) ... >>> p = re.compile(r"\d+") >>> p.sub(hexrepl, 'MAC address: 145400865868218') 'MAC address: 0x843dc6f5c9ba' Listing 9-82.Decimal to Hexadecial ``` 同样,出于演示的目的,您已经使用`sub`方法编写了一段 Python 代码,将十进制 MAC 地址转换回十六进制数。`0x`开头的数字表示这个数是十六进制数。 如果你是正则表达式的新手,这一章可能会很难。但是正如本章开头提到的,在一般编程中,掌握正则表达式是必须的,而不是可选的。 要获得关于这个主题的更多帮助,请访问下面的正则表达式备忘单。 网址: [` cheatography。com/Dave child/cheat-sheets/regular-expressions/`](https://cheatography.com/davechild/cheat-sheets/regular-expressions/) 网址: [` web。麻省理工学院。edu/hackl/www/lab/Turk shop/slides/regex-cheat sheet。pdf`](http://web.mit.edu/hackl/www/lab/turkshop/slides/regex-cheatsheet.pdf) ## 摘要 有些人会发现这一章是最难消化的一章。我不期望任何人一次阅读就能完全理解这一章。你将不得不一遍又一遍地回到这一章,慢慢地让正则表达式语法和概念渗透到你的大脑中。你刚刚完成了第九章的所有练习,理想情况下,你每周都能找到一些空闲时间来练习正则表达式。随着您编写 Python 代码,您将不得不存储和处理越来越多的数据。网络可编程性的真正力量也在于数据处理的力量。在下一章中,您将完成 Python 网络自动化的必要实验准备和集成。 # 十、GNS3 基础 如果你不自己建造实验室,那么你就不知道实验室里的一切是如何工作的。建立你自己的虚拟实验室是掌握特定技术的最好方法之一。在本章中,您将学习如何使用 GNS3 和 VMware Workstation Pro 安装和构建一个简单的网络实验室。本章将指导您正确安装和配置 GNS3 的每个步骤,以充分利用您的设置。您还将了解 Windows 上 GNS3 的各种功能。如您所知,现在不需要基于硬件的网络设备。完成简单的实验拓扑后,您将集成一个旧的 IOS 映像,出于验证目的对实验进行测试,并熟悉实验设置。 在本章中,您将通过搭建简单的 IOS 实验室来学习 GNS3 基础知识。这里学到的 GNS3 技能将允许您在 GNS3 上构建各种网络自动化实验室。您可以将这些实验室的用途扩展到您的概念验证(POC)实验室或供应商网络认证工作中。 ![img/492721_1_En_10_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Figa_HTML.jpg) ## GNS3 概览 ![img/492721_1_En_10_Figb_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Figb_HTML.jpg)在第六章结束时,您下载了适用于您的 GNS3 版本的 GNS3 `VM.ova`文件,然后将其作为虚拟服务器导入,以托管您的网络设备。如果您尚未完成此任务,则必须返回并完成“从下载和安装 GNS3 VM”一节中详细介绍的任务。ova 文件。”然后回到这一章继续。 在本章和本书中,术语思科 CML 和思科 VIRL(或 CML 和 VIRL)可互换使用。它们基本上是在虚拟环境中模拟思科网络设备的相同软件。VIRL 代表虚拟互联网路由实验室,CML 代表思科建模实验室。CML 在 2020 年取代了 VIRL。 GNS3 是 2008 年推出的多厂商网络仿真器。受 Windows、Linux 和 macOS 支持,GNS3 也被称为图形用户界面(GUI) Dynamips。你只需要理解基于 GUI 的 GNS3 运行在 Dynamips 之上。GNS3 是在社区基础上开发和支持的,您可以在 GNS3.org 网站上注册用户后免费下载。对于本章,您需要最新版本的 GNS3,以便在 VMware Workstation 上将 GNS3 作为虚拟服务器运行。建议将 GNS3 作为虚拟机在 VMware Workstation Pro 上运行,这是我们实验室的最佳实践。当在主机操作系统上将 GNS3 作为本地服务器运行时,您的 Windows 性能会显著下降;在 VMware 工作站上将 GNS3 作为单独的虚拟机运行总是更好;这将防止主机 PC 和 GNS3 服务器之间出现任何 CPU 和内存争用问题。使用 GNS3 的另一个原因是用 Linux 虚拟机创建一个完全集成的实验室。您可以使用 Linux 服务器作为 Python 服务器来控制各种 PoC 实验室中的虚拟路由器和交换机,以研究和测试网络概念。 在 Windows 机器上安装 GNS3 时,你只需要一个安装`.exe`文件,但是在安装过程中会自动安装一些额外的软件。在安装过程中,您的计算机必须直接连接到互联网。本书中显示的安装过程包含了我多年来学到的技巧,建议您严格遵循每个步骤来完成本章中的所有任务。 要测试路由器、交换机或防火墙,您需要单独找到该软件,并将其快速集成到 GNS3 中用于各种实验。GNS3 不支持所有 IOS/IOS-XE/IOS-XR 版本;出于测试目的,它仍然支持较旧的 IOS 12.x 火车。虽然支持 Cisco 的 7200 路由器 IOS 15.x 版本,但 7200 路由器 IOS 15 . x 版本存在两个明显的问题;它占用计算机的 CPU 和内存资源,并且不支持第二层交换功能。思科的 IOU 已经提供给内部的思科 TAC 工程师,IOU 的副产品似乎是新的 VIRL L2 和 L3 网络研究软件。思科最近将 VIRL 更名为 CML 个人版(PE)。新的 CML-PE L2 和 L3 映像与之前的 VIRL 映像相同,因此在本书中,您可以使用思科的任何一个映像。 GNS3 为其他软件集成提供了更多有用的功能,对于网络学生和工程师来说,它是一个很好的学习工具。CME-PE、VIRL 和 IOU 图像支持使用 Qemu 作为新 GNS3 的主要功能。还支持通过 Docker 映像集成设备。没错,在 GNS3 中,Docker 和虚拟化程序可以在 GNS3 虚拟机服务器上并行运行。自从引入 IOU 和 VIRL 以来,配置此类实验设置的最大优势就是通过 L2 IOSv 镜像实现第 2 层交换功能。GNS3 继续支持 CML-PE 的 L2 IOSv 和 L3 IOS 映像。如果你想学习思科路由和交换,我强烈推荐使用思科 CML-PE 或 EVE-GN 实验室解决方案。如果你是 GNS3 的新手,你会喜欢本章的安装程序;大多数安装过程都很简单。如果您是 GNS3 的长期用户,那么您将在本章中学习各种安装和配置技巧,并提高您现有的 GNS3 集成技能。 第一章简要讨论了 GNS3,同时比较了用于网络研究的各种仿真和模拟工具。此外,我们还研究了每种工具的优缺点。在第六章的最后,你学习了如何从一个`.ova`文件构建一个虚拟机,即一个 GNS3 VM`.ova`文件是预装的虚拟机,用于更快的部署。作为虚拟机创建的一部分,您已经下载并创建了一个名为 GNS3 VM 的虚拟机。您已经慢慢掌握了 Python、虚拟化、Linux 管理和正则表达式方面的基本 IT 技能,为 Python 和网络自动化做好了准备。从本章开始,您将构建一个环境,在这个环境中,您可以使用 Python 学习、测试、验证和掌握各种网络自动化场景。 本章中的 GNS3 安装是我们在第六章中结束的地方的延续。让我们先安装 GNS3 来集成本章中的 Cisco IOS,然后将 CML-PE L2 和 L3 IOSv 软件与 Windows 主机 PC 和 Linux 虚拟机集成。 ## 首次安装 GNS3 在第六章的 VMware Workstation 15 Pro 上创建虚拟机 GNS3 VM 是为 GNS3 与 VMware Workstation 和 Cisco 软件的集成做准备。思科的 IOS 和 VIRL 映像将由 GNS3 软件配置和控制,但实际的映像将从 GNS3 虚拟机运行,并在 VMware 工作站上运行。一个虚拟机在另一个虚拟机内运行被称为*嵌套*虚拟化环境。 在下载用于 Windows 10 的 GNS3 安装文件之前,请检查您的 GNS3 虚拟机的安装版本,并确保您下载的版本与您的 GNS3 虚拟机版本一致。如果你想要最新最好的 GNS3 版本,那么你可以从 GNS3 官方网站或 GitHub 网站下载 GNS3 安装文件和 GNS3 `VM.ova`文件。对于这本书,我已经下载了 2.2.11 版本的文件,这是在写本章时的最新版本。由于较新的 GNS3 软件定期发布,版本 2.2.11 或较新版本的 GNS3 将工作得很好。 ### 下载 GNS3 安装文件 如果您尚未下载 GNS3 安装文件,则首先转到位于 [`https://github.com/GNS3/gns3-gui/releases`](https://github.com/GNS3/gns3-gui/releases) 的 GNS3 下载 URL,并为您的 GNS3 VM 版本下载正确的 GNS3 `.exe`文件(参见图 10.1)。本书下载的版本是 2.2.11 ( `GNS3-2.2.11-all-in-one.exe`)。您可以放心地假设本书中使用的 GNS3 VM 版本也是版本 2.2.11,并且该虚拟机已经预安装在 VMware Workstation 15 上。如果您希望完全按照本书中的步骤操作,您可以使用相同的软件版本,但是如果您使用的是 VMware Workstation 16 Pro 和最新的 GNS3 文件,安装和设置过程将几乎完全相同,因此您不应该在设置 GNS3 环境时花费太多精力。 ![img/492721_1_En_10_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig1_HTML.jpg) 图 10-1。 用于 Windows 的 GNS3 安装文件下载 在这个例子中,使用 Assets 按钮通过开发者的 GitHub 网站下载软件。或者,在注册用户帐户后,可以从 GNS3.org 网站下载相同的软件。 ## GNS3 安装和设置 作为安装的一部分,GNS3 将安装基本软件,如 Microsoft Visual c++ 2017 re distributable(x64)、Wireshark、WinPcap 和 nPcap。假设你的电脑已经安装了必要的软件。在这种情况下,GNS3 安装向导将自动检测软件,但如果没有安装软件,它将自动安装必要的软件。在大多数安装情况下,GNS3 安装将顺利完成,但如果您的安装不顺利,您可以在 GNS3 安装之前预安装正确版本的 Wireshark、WinPcap 和 nPcap。您还将被提供注册和安装网络安全管理软件产品软件;在我看来,这是膨胀软件,所以如果你关心你的计算机的性能,确保你拒绝网络安全管理软件产品的免费提供。 ### GNS3 安装 最基本的安装任务已全部记录下来,并作为附加安装指南提供,以便在本书中包含更多高质量的内容。在 Windows 主机上安装 GNS3 非常简单,只需点击几次“下一步”按钮,直到安装完成。GNS3 的初始安装步骤请参考`Chapter 10 Pre-task – Installing GNS3.pdf`;安装指南可在 [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) 获得。 ![img/492721_1_En_10_Figd_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Figd_HTML.jpg) **下载并完成 GNS3 安装,然后继续下一部分!** ### GNS3 设置程序 现在我们必须设置 GNS3,以便您可以使用它来构建您的网络实验室。还有其他设置选项可用,但由于我们已经在 Windows 主机 PC 上导入了 GNS3 虚拟机,这将是我们首选的设置方法。按照说明操作,开始 GNS3 设置。 | # | 工作 | | --- | --- | | one | As soon as GNS3 launches, you will be prompted with the GNS3 server Setup Wizard. Here, we are using the GNS3 VM, so you must choose “Run appliances in a virtual machine.” Check the “Don’t show this again” box and click the Next button. See Figure 10-2.![img/492721_1_En_10_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig2_HTML.jpg)图 10-2。GNS3 设置,GNS3 服务器设置向导 | | Two | Under “Local server configuration,” leave everything as the default. GNS3 on Windows uses TCP port 3080 for the server to localhost communication. This number is rarely altered unless you have an installation problem or want to configure it differently from out-of-the-box installation. Click the Next button again. See Figure 10-3.![img/492721_1_En_10_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig3_HTML.jpg)图 10-3。GNS3 设置,本地服务器配置 | | three | There is nothing to select for the local server status, so click the Next button to proceed. See Figure 10-4.![img/492721_1_En_10_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig4_HTML.jpg)图 10-4。GNS3 设置,本地服务器状态 | | four | The default GNS3 RAM recommendation is 2GB (2048MB); update this to 4GB (4096MB) or more based on your computer specification. My computer has 32GB memory, so my host will still have plenty of memory after allocating 8GB (8192MB) of memory to the GNS3 VM. If you have 16GB of memory, the recommendation is between 4GB and 6GB of memory to this VM. When you reset the memory allocation here, it will be reflected on VMware Workstation’s GNS3 VM server. If you are using an older CPU, leave the vCPU cores at 1, but if the CPU is newer, the vCPU cores can be increased to 2 or more. Click Next. See Figure 10-5.![img/492721_1_En_10_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig5_HTML.jpg)图 10-5。GNS3 设置、GNS3 VM | | five | As soon as you click the Next button, the GNS3 VM on the workstation will boot up. And once it goes through the Power-On Self-Test (POST) , you will see a blue welcome screen similar to Figure 10-6 on VMware Workstation 15 Pro. The GNS3 VM is a Linux (Ubuntu) appliance server configured to run various GNS3-compatible appliances from many vendors. GNS3 is a lot more than just a Cisco IOS emulator and has many use cases for studying other vendor products. Your GNS3 VM will be launched automatically; give it enough time to stabilize.![img/492721_1_En_10_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig6_HTML.jpg)图 10-6。GNS3 设置,工作站上的 GNS3 VM 服务器已启动 | | six | The GNS3 Setup Wizard summary window appears, and you can review and click the Finish button to complete your setup. See Figure 10-7.![img/492721_1_En_10_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig7_HTML.jpg)图 10-7。GNS3 设置,设置摘要 | | seven | Now you will see the GNS3 GUI with the GNS3 VM and your hostname (local host) with green status lights. If you see a red or amber light, it is an indication that your GNS3 installation has not been completed cleanly, and you may need to review your installation and setup processes and troubleshoot the issue. See Figure 10-8.![img/492721_1_En_10_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig8_HTML.jpg)图 10-8。GNS3,第一次启动和运行 | ## 熟悉 GNS3 现在已经安装并启动了 GNS3,您可以开始使用该应用了。在进入 GNS3 并创建测试网络拓扑之前,让我们快速研究一下用户界面,以便更好地理解每个菜单的功能和选项。主下拉菜单选项中提供了所有图标菜单和更多菜单选项。以下部分是 GNS3 菜单的快速介绍和一些提示,可帮助您从 GNS3 顺利运行网络实验室。 ### GNS3 菜单和 GUI 图 10-9 显示了 GNS3 的主窗口,表 10-1 描述了每个选项。熟悉每个菜单的位置以及 GNS3 GUI 界面上最常用功能的图标位置,将有助于您在主机上使用 GNS3 时节省时间。 表 10-1。 GNS3 GUI 元素 | # | 菜单 | 说明 | | --- | --- | --- | | ① | 菜单选项 | 使用菜单访问下拉菜单中的所有选项。 | | ② | 文件选项 | 打开或保存 GNS3 项目。 | | ③ | 设备和拓扑选项 | 管理快照,显示/隐藏连接器,并控制设备。 | | ④ | 模拟选项 | 打开/关闭、暂停和重启设备。 | | ⑤号 | 绘图工具 | 用绘图工具装饰 GNS3 拓扑画布。 | | ⑥ | 设备类型图标 | 根据不同的设备类型显示设备。 | | ① | 添加链接工具 | 连接各种设备。 | | ⑧ | 设备列表 | 列出已安装的设备。使用搜索菜单过滤显示的设备。 | | ⑵ | 拓扑画布 | 在画布上选择并拖放设备,创建一个工作拓扑。 | | ⑩ | 日志窗口 | 显示 GNS3 操作状态。您也可以使用此窗口发送一些命令。 | | ⑪ | 拓扑/节点摘要 | 显示当前 GNS3 设备摘要。 | | ⑫ | 服务器摘要 | 显示 GNS3 虚拟机和本地服务器状态。此外,它还显示每台服务器的 CPU 和内存利用率。 | ![img/492721_1_En_10_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig9_HTML.jpg) 图 10-9。 GNS3 GUI ### 正常关闭 GNS3 和 GNS3 虚拟机 在您开始在 GNS3 的拓扑画布上绘制图标之前,这是一个友好的提醒。由于 GNS3 对 Windows 安装的敏感性以及软件试图模拟物理硬件的方式,运行在 Windows 操作系统上的 GNS3 从来都不是 100%可靠的。为了避免麻烦和排除一些小问题的时间,我必须强调,当你在 Windows 操作系统上启动和关闭 GNS3 时,你应该采取更温和的方法。当您启动 GNS3 时,您必须给它足够的时间来启动其所有后台服务,如果您使用 GNS3 VM 来托管所有虚拟网络设备,还必须启动 GNS3 VM。最重要的是,在关闭 GNS3 时,您必须遵循一套严格的程序,以避免任何配置丢失,并避免由于应用突然关闭导致的文件损坏而删除整个项目。GNS3 是学习基本网络概念的最佳免费学习工具之一,但不幸的是,它不具备企业级的可靠性。 在完成 Cisco 或任何其他供应商的 GNS3 实验后,首先保存运行配置至关重要。对于 Cisco IOS 设备,运行`copy running-config start-config`或`write memory`将路由器或交换机的运行配置保存到启动配置。之后,使用红色大停止按钮或单独选择每个设备来关闭每个设备,使任何其他设备处于关闭状态(参见图 10-10 )。一旦保存了所有配置并且正确关闭了设备,那么您可以使用右上角的 X 优雅地关闭 GNS3(参见图 10-11 )。 ![img/492721_1_En_10_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig10_HTML.jpg) 图 10-10。 GNS3,使用右键单击选项停止设备 GNS3 关闭后,运行在 VMware Workstation 15 Pro 上的 GNS3 虚拟机也将自动关闭。最好不要从工作站关闭虚拟机;建议您使用右上角的 X 标记关闭 GNS3 应用。随着您对 GNS3 及其古怪之处的熟悉,这个关闭过程成为了一个标准程序。如果不小心,您的工作实验室配置和项目可能会由于不正确的关闭程序而经常出现故障。 ![img/492721_1_En_10_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig11_HTML.jpg) 图 10-11。 GNS3,使用 X 键正常关机 ### 以管理员身份启动 GNS3 在 Windows 操作系统上首次启动 GNS3 时,这里有一个快速提示。当 GNS3 安装在您的 Windows PC 上时,如果您没有取消选择此选项,默认情况下将在您的 Windows 桌面上创建 GNS3 启动图标。使用此图标启动 GNS3 是启动程序的最快方式;但是,不以管理员身份启动 GNS3 会限制您对网络适配器设置和其他设置进行 GNS3 配置更改。因此,最佳实践是始终以管理员身份启动 GNS3。作为 GNS3 管理员,有一个小贴士可以改变 GNS3 图标下的设置。 | # | 工作 | | --- | --- | | one | First, go to the desktop of your host PC, locate the GNS3 icon, and right-click it to open Properties. See Figure 10-12.![img/492721_1_En_10_Fig12_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig12_HTML.jpg)图 10-12。GNS3,Windows 主机桌面上的开始图标 | | Two | Now, go to the Compatibility tab. Under Settings , select the “Run this program as an administrator” option, click the Apply button, and click the OK button to save the settings. See Figure 10-13.![img/492721_1_En_10_Fig13_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig13_HTML.jpg)图 10-13。GNS3,更改程序图标启动设置 | 现在你已经准备好学习一些 GNS3 技巧来提高你的技能。在本章的其余部分,将向您介绍一些很酷的 GNS3 技巧,例如将 GNS3 设备连接到主机、将 GNS3 连接到互联网、将 Microsoft loopback 添加到实验拓扑中,以及克隆 GNS3 项目以制作基础实验模板。 ## 首次使用 GNS3:思科 IOS 和 Windows 实验室 最初,学习思科技术和认证的唯一方法是使用基于物理硬件的实验室,其中有一堆路由器和交换机。当 Christophe Fillot 在 2005 年 8 月开发 Dynamips 来模拟旧的 Cisco 路由器平台时,它永久性地改变了网络专业学生学习 Cisco 技术的方式。在 Dynamips 之前,有 Cisco 的 PacketTrace,但它更多的是一个模拟器,而不是仿真器。如前所述,GNS3 是 Dynamips 的 GUI 版本,GNS3 的底层应用仍然是 Dynamips。 最近,一个众所周知的秘密是 Cisco 正在使用基于 UNIX 的 Cisco IOU 进行内部员工培训,但是 IOU 并没有作为产品在市场上发布。有一段过渡期,思科 IOU 被泄露给思科合作伙伴,联网的同学都在用思科 IOU。思科最近将思科 IOU 重新解释为思科 CML(之前的 VIRL ),并将其发展为一种基于订阅的产品,可作为市场上的付费产品。 在 GNS3 开发人员集成 VIRL 和 IOU 映像之前,老派网络学生在 GNS3 上使用有点过时的思科 IOS 映像来学习思科认证。即使在今天,GNS3 也支持较旧的 Cisco IOS 映像;这与 CCNA 研究相关,因为它提供了不使用任何物理设备来模拟超过 90%的 CCNA 路由研究的方法。尽管网络技术和网络教育市场有了一些新的进步,但许多旧的路由和交换概念仍然与 CCNA 研究相关,因此在学习基本网络概念时,Cisco IOS 12.4.x 版本将足以满足您的学习需求。 使用旧的基于 IOS 的 GNS3 实验室学习网络有两个主要缺点。首先,它只能在大多数路由器平台上支持较旧的 IOS 12.4 版本,但 Cisco 7200 除外。其次,第 2 层对交换的支持只能通过安装在路由器平台上的 NM-16ESW 模块来实现。使用较新的 VIRL L2 和 L3 映像,您可以在 GNS3 上构建功能更多的思科路由和交换实验室,并具备许多 IOS 功能。 作为 GNS3 引入的一部分,将旧的 Cisco IOS 集成到实验室将是一个很好的起点。实际上,在 VIRL 集成中,唯一被替换的是 IOS 映像和更新的 VIRL 映像。本章将逐步向您介绍一些可以在运行于 GNS3 的 Cisco IOS 上使用的技术。在很大程度上,本章中学到的所有技术都可以扩展到第 12 、 13 和 14 章中的 CML 实验室,它们将帮助你建立各种更有效、更灵活的 GNS3 实验室。即使你是一名老 CCNA 学生,熟悉 GNS3 上的旧 IOS 集成,也不要跳过这一章,因为在这一章中你会学到一些新技能。 让我们先快速讨论一下思科软件许可模式,包括思科 IOS、IOU 和 VIRL 映像。 ### Cisco IOS 软件许可和下载旧的 Cisco IOS 首先,要使用 Cisco IOS 配置您的 GNS3 实验室,您必须找到并下载支持 GNS3 的 Cisco 路由器型号的 Cisco 互联网操作系统(IOS)映像。如前所述,这本书的工作假设要么你已经完成了思科 CCNA R&S 认证研究或你目前正在完成思科的助理级认证研究。另一个假设是,你对思科如何开展业务感兴趣,你想在未来的许多年里作为一名网络/安全/数据中心工程师在企业网络领域工作。思科通过销售硬件、软件和服务支持合同来增加收入。要了解思科如何通过软件增加收入,您必须了解思科软件的软件所有权和许可协议。要理解思科的软件许可模式,你必须从思科的商业模式着手。您已经知道,思科是面向企业和中小型企业市场销售网络设备的领先供应商;它出售其硬件和软件的技术支持合同包,以提高公司的利润率。技术支持协议还包括使用思科软件的权利。换句话说,如果您通过正规渠道从思科的合作伙伴那里购买了设备,那么作为客户,您有权使用在您的网络设备上运行的特定平台软件,并且如果您需要技术支持,可以获得思科 TAC 的全天候支持,直到服务合同的最后一天。 其次,请注意,如果您购买了运行 Cisco IOS 版本 15 的设备,通常您只允许在同一软件版本内进行较小的补丁升级,除非有安全漏洞的现场通知或升级到最新主要软件版本的现场通知。通常,您的设备会老化,新软件的最低硬件要求也会提高。你将被限制升级设备的软件到某个级别,你被迫升级到最新的硬件型号。随着思科设备即将停产(EoL),思科将不再支持任何 EoL 设备。思科的大部分软件被视为思科专有,是思科知识产权的一部分,因此思科拥有所有的 IOS 和其他软件权利;因此,不允许个人自由分发此软件。通过渠道合作伙伴购买思科设备后,技术支持服务合同还允许从思科 TAC 下载软件和获得技术支持。如果您目前为思科合作伙伴工作,您将能够轻松下载最新的 IOS 软件,但可能无法下载较旧的 IOS 软件。为你的研究获取旧 IOS 的另一个好方法是从易贝、Craigslist 或 Gumtree 等在线拍卖网站购买二手实验室路由器,甚至从你认识的不再使用他的旧 CCNA/CCNP 实验室设备的人那里购买。你会在你设备的闪存中找到一个正常工作的 IOS,IOS 可以通过 TFTP 文件传输复制到你的电脑上;那么它可以用作 GNS3 IOS 基础映像。即使您有权从思科网站下载 IOS,从思科的官方网站找到一个旧的 GNS3 兼容的 IOS 也是一个挑战,因为大多数旧设备已经停产。假设你是一名全日制学生,想从事网络行业,做网络或安全工程师。在这种情况下,谷歌搜索可以带你到一些网站,在那里你可能能够找到旧版本的 IOS。这本书使用了我的旧思科设备上的旧版本 IOS,但是最终下载旧版本 IOS 的工作副本是你的责任。 第三,当您访问 GNS3 的官方网站时,您将首先注意到 GNS3 支持较旧的思科路由器,即 C1700、C2600、C2691、C3600、C3700 和 C7200 系列路由器以及较旧样式的思科 PIX 防火墙。出于快速演示的目的,本章使用了旧的 Cisco 3725 路由器的 IOS 版本 12.4.15 T15,该版本与最新的 GNS 3.2.11 兼容。虽然支持 Cisco 7200 路由器的 IOS 版本 15.x,但 RAM 要求为 512MB,比旧的 3700 系列路由器消耗更多的计算机内存。如果您有一台 CPU 和内存相对较强的 PC,您可以选择使用 Cisco 7200 IOS 版本 15.x 来代替 3725 的 IOS。在本章中,使用的确切 IOS 版本是`c3725-adventerprisek9-mz.124-15.T14.bin`映像,这是 GNS3.2.11 当前支持的版本。 以下是一些旧版本的 IOS,您可以在我们的学习中使用: * `c3725-adventerprisek9-mz.124-15.T14.bin`(旧) * `c3745-adventerprisek9-mz.124-25d.bin`(旧) * `c7200-adventerprisek9-mz.124-24.T5.bin`(旧) * `c7200-adventerprisek9-mz.152-4.M7.bin`(更新) 您负责购买和下载本书中所需的思科软件。本书不提供本书中使用的任何软件,但会向您指出查找和下载正确软件的来源。在一天结束的时候,每个读者都要为他们自己的学习需要对任何软件的使用负责。 ### 解压缩 Cisco IOS 以供 GNS3 使用 如果你想跟随这本书,你应该已经成功地在你的`Downloads`文件夹中找到并下载了`c3725-adventerprisek9-mz.124-15.T14.bin`。然后,您将把`.bin`文件解压缩成`.image`文件,这样解压缩后的图像文件可以在 GNS3 中使用。在不同的操作系统上有不同的方法来解压缩 Cisco IOS `.bin`文件,但是您将学习如何使用两种最常见的 Windows 方法来解压缩 IOS 文件。第一种方法是使用`Unpack-0.1_win.zip`,第二种方法是将 GNS3 集成到 Dynamips 中对 IOS 进行解压缩。 #### 使用 Unpack.exe 方法解压缩 Cisco IOS 在这里,您将为 Windows 下载`Unpack.exe`,并解压缩一个 Cisco IOS 文件供 GNS3 使用。按照这些简单的说明解压缩原始的`.bin`文件,并保存为一个`.image`文件,供 GNS3 使用。请注意,该文件必须以`.image`文件扩展名保存,并在本任务结束时移动到您 PC 的`Downloads`文件夹中。 | # | 工作 | | --- | --- | | one | 首先,下载`Cisco image unpacker 0.1 binary for Windows`到你的`Downloads`文件夹,并解压到`Downloads`文件夹下。文件名是`Unpack-0.1_win.zip`,可以从 [SourceForge 下载。网](http://sourceforge.net)。URL: [`https://sourceforge.net/projects/gns-3/files/Cisco%20Image%20Unpacker/v0.1/`](https://sourceforge.net/projects/gns-3/files/Cisco%2520Image%2520Unpacker/v0.1/) | | Two | If you still have your IOS file in the `Downloads` folder, move it to the `Unpack` folder, as shown in Figure 10-14.![img/492721_1_En_10_Fig14_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig14_HTML.jpg)图 10-14。解压缩 IOS,将 IOS 文件移动到解压缩文件夹 | | three | 在 Windows 命令行提示符下,运行下面的`unpack.exe`命令将文件解压到同一个文件夹中。一旦文件被解压缩,解压缩后的文件名将使用`.bin.unpacked`作为其文件扩展名:`unpack.exe --format IOS [Your_IOS_Name]`。`C:\Users\your_name\Downloads\Unpack>unpack.exe --format IOS c3725-adventerprisek9-mz.124-15.T14.bin`**注意**用你的用户名替换`your_name`。 | | four | Now rename the `.bin.unpacked` file as a `.image` file. So, the filename changes from `c3725-adventerprisek9-mz.124-15.T14.bin.unpacked` to `c3725-adventerprisek9-mz.124-15.T14.image`. Also, notice that the decompressed `IMAGE` file is almost twice the size of the `.bin` file. See Figure 10-15.![img/492721_1_En_10_Fig15_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig15_HTML.jpg)图 10-15。解压缩 IOS,将解压缩的. bin.unpacked 文件重命名为。图像 | | six | Once the filename has been updated with the correct file extension, copy and paste the file into the `Downloads` folder again. This step is essential as GNS3 will be searching for a compatible `.image` file in the `Downloads` folder on your Windows. See Figure 10-16.![img/492721_1_En_10_Fig16_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig16_HTML.jpg)图 10-16。解压缩 IOS,移动。图像文件到下载文件夹 | 如果您想了解如何使用第二种方法(Dynamips 方法)解压缩 IOS 文件,请阅读 10.4.2.2,但是如果您想立即开始网络实验构建任务,请跳到 10.5。 #### 使用 Dynamips 方法解压缩 Cisco IOS(可选) 按照这些说明,使用 Dynamips 服务器方法将 IOS `.bin`文件解压缩到`.image`文件中。一旦你完成了任务,确保`.image`文件的副本已经放在你的主机的`Downloads`文件夹下。一些读者可能会发现这个过程比第一种解压方法更容易管理。 | # | 工作 | | --- | --- | | one | If you have not started your GNS3 and GNS3 VM, first launch it by double-clicking your desktop’s GNS3 start icon. See Figure 10-17.![img/492721_1_En_10_Fig17_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig17_HTML.jpg)图 10-17。GNS3,GNS3 桌面图标 | | Two | Give one to two minutes for the GNS3 VM to start up properly and settle down. Wait until both GNS3 VM and your local server’s CPU and RAM levels settle down. See Figure 10-18.![img/492721_1_En_10_Fig18_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig18_HTML.jpg)图 10-18。GNS3,服务器摘要窗口 | | three | GNS3 will prompt you with the Project window. Since we are interested only in decompressing the IOS, we will click the Cancel button. See Figure 10-19.![img/492721_1_En_10_Fig19_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig19_HTML.jpg)图 10-19。GNS3,项目窗口取消 | | four | To open the Preferences menu on GNS3, select Edit ➤ Preferences. See Figure 10-20.![img/492721_1_En_10_Fig20_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig20_HTML.jpg)图 10-20。GNS3,打开首选项菜单 | | five | Select the IOS routers under Dynamips, as shown in Figure 10-21.![img/492721_1_En_10_Fig21_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig21_HTML.jpg)图 10-21。GNS3、dynamips、IOS 路由器 | | six | Click the New button to open the New IOS router template window. In the Server screen, select “Run this IOS router on my local computer.” You can also select the “Run this IOS router on the GNS3 VM” option, but we’ll use the old method and remove the image after decompressing the file. See Figure 10-22.![img/492721_1_En_10_Fig22_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig22_HTML.jpg)图 10-22。GNS3,新的 IOS 路由器模板 | | seven | In the IOS image window, click the Browse button on the right. See Figure 10-23.![img/492721_1_En_10_Fig23_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig23_HTML.jpg)图 10-23。GNS3,选择 IOS 映像 | | eight | Select your IOS image in the `Downloads` folder and click the Open button. See Figure 10-24.![img/492721_1_En_10_Fig24_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig24_HTML.jpg)图 10-24。GNS3,选择 IOS 并打开 | | nine | Click Yes to decompress the IOS image. See Figure 10-25.![img/492721_1_En_10_Fig25_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig25_HTML.jpg)图 10-25。GNS3,解压缩 IOS 映像 | | Ten | 当你回到 IOS 镜像窗口时,GNS3 会告诉你`.image`文件将被存储在哪里。在本演示中,它被提取到用户的`GNS3\images\IOS\`文件夹中。见图 10-26 。`C:\Users\your_name\GNS3\images\IOS\c3725-adventerprisek9-mz.124-15.T14.image`![img/492721_1_En_10_Fig26_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig26_HTML.jpg)图 10-26。GNS3,解压缩图像位置**注意**用你的名字代替`your_name`。 | | Eleven | 对于名称和平台,将所有设置保留为默认值,然后单击 Next 按钮。 | | Twelve | The recommended RAM size for 3725 is 256MB, but GNS3 will default the RAM to 128MB. This is only an example to demonstrate how to extract the `.bin` file into the `.image` file, so you can leave it as 128MB and click the OK button. See Figure 10-27.![img/492721_1_En_10_Fig27_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig27_HTML.jpg)图 10-27。GNS3,内存 | | Thirteen | 在“网络适配器”窗口中,单击下一步按钮。 | | Fourteen | 在“WIC 模块”窗口中,再次单击下一步按钮。 | | Fifteen | 在空闲 PC 窗口中,单击下一步按钮。参见图 10-28 。**注意**如果您选择在本地机器和 Dynamips 上运行 IOS 路由器,您可以单击 Idle-PC finder 按钮来查找合适的 Idle-PC,它会自动为您的系统找到一个最佳值。由于我们将配置 IOS 路由器从 GNS3 虚拟机运行,我们可以跳过这一过程。![img/492721_1_En_10_Fig28_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig28_HTML.jpg)图 10-28。GNS3,IOS idle-PC finder 示例 | | Sixteen | Now we extracted the `.image` file in a folder. We plan to run most of the devices on the GNS3 VM, so select the router and click the Delete button to remove the router from Dynamips ➤ IOS routers. Then, click OK to close the window. See Figure 10-29.![img/492721_1_En_10_Fig29_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig29_HTML.jpg)图 10-29。GNS3,选择和删除 IOS 路由器 | | Seventeen | 现在转到以下文件夹路径:`C:\Users\your_name\GNS3\images\IOS`。您会发现扩展名为`.image`的解压缩 IOS 镜像文件的副本。将文件复制到`Downloads`文件夹。 | Note **注意**用你的用户名替换`your_name`。参见图 10-30 。 ![img/492721_1_En_10_Fig30_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig30_HTML.jpg) 图 10-30。 主机,移动。图像文件到下载文件夹 现在,您可以在 GNS3 虚拟机上安装 Cisco IOS 路由器了。让我们坚持完成这项任务。 ## 在 GNS3 虚拟机上安装 Cisco IOS 您已经成功解压缩了 IOS 映像,现在可以使用`.image`文件在本地服务器(PC)或 GNS3 VM 服务器上创建一个 GNS3 项目。由于我们使用 GNS3 VM 作为我们的 GNS3 服务器,并希望安装大多数设备,这避免了与本地 Dynamips 服务器设置相关的高 CPU 利用率、空闲 PC 和内存争用问题。我们将在 GNS3 虚拟机上安装 IOS,而不是本地服务器。这是一个推荐的安装,因为与在本地服务器上运行设备相比,GNS3 虚拟机的性能对主机性能的 CPU 和内存影响较小。 如果您的 GNS3 尚未运行,请启动应用,让它在工作站上初始化 GNS3 VM。等待大约 1 到 2 分钟,让 GNS3 虚拟机正常启动。等到你电脑的 CPU 和内存稳定下来。 假设工作站上的 GNS3 和 GNS3 VM 都在您的电脑上正常运行,让我们继续完成以下任务,创建我们的第一个 GNS3 项目,并在 GNS3 VM 服务器上安装`.image`文件: | # | 工作 | | --- | --- | | one | 启动 GNS3 后,当 GNS3 项目窗口出现时,创建一个新的 GNS3 项目并命名。或者,您可以使用“新建项目”图标打开“项目”菜单并开始此任务。You can give any meaningful name of your choice. Here, the project name given was `ios_lab`. You do not have to follow this naming convention strictly. See Figure 10-31.![img/492721_1_En_10_Fig31_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig31_HTML.jpg)图 10-31。GNS3,创建新项目 | | Two | From the GNS3 menu, select File ➤ + New template. Or, click the Router icon under Devices on the left and click + New template. See Figure 10-32.![img/492721_1_En_10_Fig32_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig32_HTML.jpg)图 10-32。GNS3,打开“新模板”菜单 | | three | When the “New template” window appears, select the "Install an appliance from the GNS3 server (recommended)" option. Click the Next button. See Figure 10-33.![img/492721_1_En_10_Fig33_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig33_HTML.jpg)图 10-33。GNS3,“新模板”选项 | | four | Use the search field to locate your IOS device or use the drop-down device menu on the “Appliances from server” screen to select your device. Once you locate your IOS device, click it to select it and then click the Install button at the bottom. See Figure 10-34.![img/492721_1_En_10_Fig34_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig34_HTML.jpg)图 10-34。GNS3,搜索你的 IOS 设备 | | five | Next, leave the “Server type” selection as the default, “Install appliance on the GNS3 VM (recommended),” and click the Next button one more time. See Figure 10-35.![img/492721_1_En_10_Fig35_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig35_HTML.jpg)图 10-35。GNS3,服务器类型选择 | | six | If you have successfully followed the previous steps, the “Required files” window will locate your `.image` file, and you will see the same or similar screen to Figure 10-36. Select the image file by highlighting the file and click the Next button again.![img/492721_1_En_10_Fig36_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig36_HTML.jpg)图 10-36。GNS3,必需文件 | | seven | Of course, you want to install the Cisco IOS on GNS3\. Click the Yes button. See Figure 10-37.![img/492721_1_En_10_Fig37_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig37_HTML.jpg)图 10-37。GNS3,装置安装确认消息 | | eight | You are almost there; click the Finish button to finish the installation. See Figure 10-38.![img/492721_1_En_10_Fig38_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig38_HTML.jpg)图 10-38。GNS3,使用提示 | | nine | If the installation is successful, you will be prompted with the message shown in Figure 10-39. Click the OK button.![img/492721_1_En_10_Fig39_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig39_HTML.jpg)图 10-39。GNS3,添加模板成功消息 | | Ten | 现在,让我们为 3725 分配正确的 RAM 大小,并为我们的实验室添加一个额外的 FastEthernet 接口。Finally, go back to the GNS3 main window and click the router icon. You will see the installed Cisco IOS router. Under the Routers icon, select the router icon and right-click and select “Configure template.” See Figure 10-40.![img/492721_1_En_10_Fig40_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig40_HTML.jpg)图 10-40。GNS3,配置模板 | | Eleven | On the “Memories and disks” tab, change the default RAM size to 256MB and PCMCIA disk0 to the recommended 64MB and then click the OK button. You can go to the Cisco Feature Navigator page to check the RAM requirement for Cisco devices. You can go to [`https://cfnng.cisco.com/archived-data`](https://cfnng.cisco.com/archived-data) and search for your platform and IOS version for older IOS devices. You can download the requirement in Excel format. See Figure 10-41.![img/492721_1_En_10_Fig41_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig41_HTML.jpg)图 10-41。GNS3,将磁盘 0 添加到 IOS 路由器 | | Twelve | On the Slots tab, add an NM-1FE-TX on slot 1\. You can also add NM-4T for four more FastEthernet interfaces or NM-16ESW, which adds 16 L2 interfaces. If you want to use this router as an L2 switch, then NM-16ESW can be added, but this is an old GNS3 method with little relevance in the newer GNS3 labs. You can also add WIC-1T or WIC-2T under the WICs option on this tab. Since you are only going to connect through FastEthernet, you can leave the WIC slots blank. Click the OK button. See Figure 10-42.![img/492721_1_En_10_Fig42_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig42_HTML.jpg)图 10-42。GNS3,在 IOS 路由器上添加接口 | | Thirteen | Now click the router icon and then drag and drop into the Topology canvas on the right. You have successfully installed an IOS image on the GNS3 VM. See Figure 10-43.![img/492721_1_En_10_Fig43_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig43_HTML.jpg)图 10-43。GNS3,检查安装 | | Fourteen | Now close GNS3 by clicking the top-right X button to close GNS3 and the GNS3 VM running on Workstation. Your GNS3 will be closed, followed by GNS3 VM shutdown. If you have another virtual machine running on VMware Workstation, the other VMs on Workstation will continue to run. See Figure 10-44.![img/492721_1_En_10_Fig44_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig44_HTML.jpg)图 10-44。GNS3,关闭 GNS3(和 GNS3 虚拟机) | ## 在 GNS3 上创建 IOS 实验室拓扑并连接到互联网 | ![img/492721_1_En_10_Figf_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Figf_HTML.jpg) | | ![img/492721_1_En_10_Figg_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Figg_HTML.jpg)为了获得一致可靠的实验结果,本节涉及的任务需要互联网连接,建议您的计算机通过以太网电缆连接,而不是无线连接。实验室通过无线适配器连接可能会导致与书中不同的结果。在大多数情况下,实验都是通过将以太网电缆连接到家用路由器的以太网端口来进行的。学完本章后,大多数实验都可以通过无线连接正常工作,但是如果您怀疑某些地方工作不正常,请考虑将您的 PC 连接到路由器或交换机上的物理以太网端口。 | 您被要求在 10.5 中关闭 GNS3 和 GNS3 虚拟机,我特意要求您关闭这些程序,以便您可以正确地重新打开之前的实验。现在,按照步骤重新打开项目,继续构建您的 IOS 实验室。 | # | 工作 | | --- | --- | | one | First, launch GNS3 using the GNS3 icon on your Windows desktop. When the GNS3 project window appears, wait for one to two minutes for the GNS3 VM to boot up. See Figure 10-45.![img/492721_1_En_10_Fig45_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig45_HTML.jpg)图 10-45。GNS3,GNS3 桌面图标 | | Two | Click the “Recent project” button under “Open project” to select the `ios_lab.gns3` project, and then click the OK button to re-open the first project. If the IOS installation and GNS3 integration were all OK, the project should normally open with a GNS3 VM kick-start on Workstation. See Figure 10-46.![img/492721_1_En_10_Fig46_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig46_HTML.jpg)图 10-46。GNS3,打开最近的项目 | | three | As soon as the `ios_lab.gns3` project opens, click the “All devices” icon on the left and drop one NAT (NAT1), two routers (R1 and R2), and two VPCS (PC1 and PC2). When you drag and drop these devices, you will be prompted to select a server to run these devices. Pay close attention to which server you select here. See Figure 10-47.![img/492721_1_En_10_Fig47_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig47_HTML.jpg)图 10-47。GSN3,“所有设备”图标对于 NAT1,我选择`pynetauto`,这是我的 PC 名;您的电脑名称会有所不同,因此您应该选择您的服务器名称。这意味着该设备将在本地服务器的 NATted 网络上运行,DHCP 服务将从 VMnet8 (192.168.183.0/24)网络中分配一个 IP 地址。You can also connect to the Internet via GNS3 VM’s NATted network (192.168.122.0/24), but connecting to the outside network via GNS3 VM sometimes could be unreliable. Using the local server NAT address via VMnet8 provides a more reliable connection to the Internet and the Linux servers running on VMnet8\. See Figure 10-48.![img/492721_1_En_10_Fig48_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig48_HTML.jpg)图 10-48。GSN3,NAT1 服务器选择 For routers and VPCSs, select GNS3 VM as the server. These devices will run on the GNS3 VM server. The GNS3 built-in service is usually used to provide a seamless virtual connection to other devices; it is not a managed switch like Cisco switches. VPCS is a handy GNS3’s built-in client, which allows the users to use them as end devices for simple ICMP tests. VPC saves a lot of computing power compared to running another virtual machine to run an end client. Using a VPC is like configuring a dummy loopback interface on a Cisco router or switch for end device reachability testing. Still, it is even better as you can still use ping and traceroute from VPCS, just like a real end device. See Figure 10-49.![img/492721_1_En_10_Fig49_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig49_HTML.jpg)图 10-49。GSN3、Switch1 和 PC1 服务器选择 | | four | Click the “Add a link” icon on the left bottom, and connect all devices as shown in Figure 10-50. Refer to the Topology and Servers summaries to connect your devices as shown.![img/492721_1_En_10_Fig50_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig50_HTML.jpg)图 10-50。GSN3,连接设备 | | five | When you are happy with your topology, click the big green play icon to start all devices. Note that if there are more than five devices, it is a best practice to right-click each device and start one device at a time to avoid the CPU and memory hangs during the device bootup. Once all devices have started, the lights on the connections should turn all green, as shown in Figure 10-51.![img/492721_1_En_10_Fig51_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig51_HTML.jpg)图 10-51。GSN3,起动装置 | | six | Make sure that all lights under the Topology and Servers summaries are green and there are no errors. See Figure 10-52.![img/492721_1_En_10_Fig52_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig52_HTML.jpg)图 10-52。GSN3,拓扑状态 | | seven | 要启动 R1 的控制台,只需双击拓扑上的 R1 图标,或者右键单击并选择控制台菜单。在控制台提示符下,让我们为第一个实验快速配置 R1。 | `hostname R1``!` | 将 R1 指定为主机名 | | `ip name-server 8.8.8.8``!` | 配置名称服务器 | | `interface FastEthernet0/0``ip address dhcp``no shut` | Fa0/0 从虚拟机网络 8 (NAT)的动态主机配置协议服务器获取互联网协议(Internet Protocol)地址调出界面 | | `interface FastEthernet0/1``ip address 192.0.2.1 255.255.255.0``no shut``!``router ospf 1` | 在 f0/1 上配置一个互联网协议(Internet Protocol)地址 连接到 R2 的 f0/0 端口 调出界面 | | `network 192.0.2.0 0.0.0.255 area 0``network 192.168.183.0 0.0.0.255 area 0` | OSPF 1 配置允许开放式最短路径优先(Open Shortest Path First Interior Gateway Protocol)广告 | | `!``do write memory` | 将 R1 的运行配置保存到启动配置 | | | eight | Now let’s configure R2\. The configuration is similar to R1, but it has three interfaces on three different subnets . 【 T55 | `hostname R2``!` | Specify R2 as the host name | | `ip dhcp excluded-address 172.168.2.1``172.168.2.21` | | `!``ip dhcp pool VPCS_2``network 172.168.2.0 255.255.255.0` | 【 T70 DHCP is, 172.168.2.22 | | `interface FastEthernet0/0` | | | `ip address 192.0.2.2 255.255.255.0``no shut``!``interface FastEthernet0/1`【 T99 0/0 is configured with an IP addressconnected to f0/ of R1\. 1 portcalls out the interface | | `ip address 172.168.1.1 255.255.255.0``no shut``!`[ 7] | At f0/ 1 configure an IP addressto connect to PC1call-out interface | | `ip address 172.168.2.1 255.255.255.0``no shut` `!``router ospf 1``network 172.168.0.0 0.0.0.255 area 0` | in f1/ 0, configure an IP addressto connect to PC2call-out interface | | `network 172.168.2.0 0.0.0.255 area 0` 】`network 192.0.2.0 0.0.0.255 area 0``!` | Configure OSPF 1 | | `ip route 0.0.0.0 0.0.0.0 192.0.2.1` for internal network advertising. T171 】`!``!` | Configure the static route for the last selected gateway; The static route to R1 | | `do write memory` | saves the running configuration of R2 to the startup configuration | | | nine | 现在打开 PC1 (VPCS)控制台,练习手动配置 PC1 的 IP 地址。键入如下所示的命令。`PC1> ip 172.168.1.11/24 172.168.1.1 <<< Assign IP address and Gateway``Checking for duplicate address...``PC1 : 172.168.1.11 255.255.255.0 gateway 172.168.1.1``PC1> ip dns 8.8.8.8 <<< Configure DNS IP address``PC1> show ip <<< Show ip address details``NAME               : PC1 1``IP/MASK            : 172.168.1.11/24``GATEWAY            : 172.168.1.1``DNS                : 8.8.8.8``MAC                : 00:50:79:66:68:00``LPORT              : 20000``RHOST:PORT         : 127.0.0.1:20001``MTU:               : 1500`现在,通过从 PC1 向 R1、R2 和互联网发送一些 ICMP 消息来验证您的配置。`PC1> ping 172.168.1.1 –c 3 <<< To the Gateway f0/1``172.168.1.1 icmp_seq=1 timeout <<< first arp drop``84 bytes from 172.168.1.1 icmp_seq=2 ttl=255 time=3.098 ms``84 bytes from 172.168.1.1 icmp_seq=3 ttl=255 time=8.093 ms``PC1> ping 192.0.2.1 -c 3 <<< To R1's f0/1``84 bytes from 192.0.2.1 icmp_seq=1 ttl=254 time=25.297 ms``84 bytes from 192.0.2.1 icmp_seq=2 ttl=254 time=29.232 ms``84 bytes from 192.0.2.1 icmp_seq=3 ttl=254 time=31.170 ms``ping 8.8.8.8 -c 3 <<< To Google DNS server, Ethernet connection recommended``84 bytes from 8.8.8.8 icmp_seq=1 ttl=126 time=50.198 ms <<< On wireless you may get timeouts``84 bytes from 8.8.8.8 icmp_seq=2 ttl=126 time=31.918 ms``84 bytes from 8.8.8.8 icmp_seq=3 ttl=126 time=58.199 ms` | | Ten | 这一次,打开 PC2 (VPCS)的控制台,使用`ip dhcp`命令配置 IP 地址。与之前的配置不同,PC2 将从 R2 的 DHCP 服务获取 IP 地址。如果您的配置完全相同,那么 PC2 的 IP 地址应该为 172.168.2.22。`PC2> ip dhcp``DDORA IP 172.168.2.22/24 GW 172.168.2.1``PC2> show ip``NAME                 : PC2 1``IP/MASK              : 172.168.2.22/24``GATEWAY              : 172.168.2.1``DNS                  : 172.168.2.1``DHCP SERVER          : 172.168.2.1``DHCP LEASE           : 86391, 86400/43200/75600``MAC                  : 00:50:79:66:68:01``LPORT                : 20002``RHOST:PORT           : 127.0.0.1:20003``MTU:                 : 1500`现在,通过从 PC2 向 R2、R1、PC1 和互联网发送一些 ICMP 消息来验证您的配置。`PC2> ping 172.168.1.1 -c 3 <<< To R2’s f0/1 interface``84 bytes from 172.168.1.1 icmp_seq=1 ttl=255 time=4.515 ms``84 bytes from 172.168.1.1 icmp_seq=2 ttl=255 time=2.110 ms``84 bytes from 172.168.1.1 icmp_seq=3 ttl=255 time=5.558 ms``PC2> ping 172.168.1.11 -c 3 <<< To PC1, no entry in the ARP table.``172.168.1.11 icmp_seq=1 timeout <<< first arp drop``172.168.1.11 icmp_seq=2 timeout <<< second arp drop``84 bytes from 172.168.1.11 icmp_seq=3 ttl=63 time=12.709 ms`注意:每当路由器必须向下一跳(或直接连接的目的地)发送没有 ARP 表条目的数据包时,ARP 请求会发出,但一个或两个原始数据包会被无条件丢弃。 | |   | `PC2> ping 192.0.2.1 -c 3 <<< To R1’s f0/1``84 bytes from 192.0.2.1 icmp_seq=1 ttl=126 time=33.306 ms``84 bytes from 192.0.2.1 icmp_seq=2 ttl=126 time=27.037 ms``84 bytes from 192.0.2.1 icmp_seq=3 ttl=126 time=23.962 ms``PC2> ping 8.8.8.8 -c 3 <<< To Google’s DNS server``84 bytes from 8.8.8.8 icmp_seq=1 ttl=126 time=51.237 ms <<< On wireless you may get timeouts``84 bytes from 8.8.8.8 icmp_seq=2 ttl=126 time=35.299 ms``84 bytes from 8.8.8.8 icmp_seq=3 ttl=126 time=24.883 ms`完成实验后,按照标准程序保存配置,关闭设备,并关闭项目。 | 您已经使用两台 Cisco IOS 路由器和两台 VPCS 完成了 GNS3 基本 OSPF 路由实验。虽然这不是一本关于路由和交换的书,但您已经建立了拓扑,配置了拓扑,并完成了以下任务: * 为路由器接口分配 IP 地址 * 配置了名称服务器 * 在 R2 上配置了 DHCP 服务 * 在 R1 和 R2 上为内部路由配置了 OSPF * 已配置的静态路由器(最后的网关) * 将运行配置保存到启动配置 此外,您还学习了如何使用手动和 DHCP 配置方法来配置和使用 GNS3 的虚拟 PC (VPCS)功能。 接下来,让我们了解如何安装 Microsoft 环回适配器,通过从主机的 Windows 操作系统与 GNS3 拓扑通信来管理 GNS3 设备。Microsoft 环回适配器帮助 Windows 主机与运行在 GNS3 上的设备通信。 ## 安装 Microsoft 环回适配器 要从主机 PC 通信、控制和管理运行在 GNS3 上的 Cisco 设备,您可以安装 Microsoft loopback 适配器,并与运行在 GNS3 上的 Cisco 路由器、交换机或其它设备通信。Microsoft loopback 适配器是用于测试目的的虚拟网络适配器,由 Windows 提供。借助 Microsoft loopback,您可以方便、可靠地从主机操作系统连接到 GNS3 设备,并与虚拟设备通信。以下是如何在你的主机上安装实验性的微软回环。假设你是一个重度 Windows 用户,需要一些时间来提高 Linux 管理技能。在这种情况下,从您的 Windows 主机访问和管理 GNS3 设备将为您提供更多的时间来练习 Linux,同时获得其他技能。 如果您正在使用装有香草味 Windows 10 操作系统的电脑,请执行以下任务。如果您使用公司提供的标准操作环境(SOE)笔记本电脑或 PC,添加 Microsoft 环回适配器可能会略有不同。如果您使用的是个人计算机,请按照以下步骤安装 Microsoft loopback。如果您使用公司提供的 SOE 笔记本电脑或 PC,菜单可能与以下示例不同。 | # | 工作 | | --- | --- | | one | 右键单击主机窗口左下角的 Microsoft 图标。当列表出现时,选择设备管理器。 | | Two | In the Device Manager window, first click Network Adapters to highlight it, then click Action located in the menu, and finally click Add Legacy Hardware. See Figure 10-53.![img/492721_1_En_10_Fig53_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig53_HTML.jpg)图 10-53。设备管理器,网络适配器,添加传统硬件 | | three | 出现“添加硬件”弹出窗口时,单击“下一步”按钮。 | | four | 在下一个窗口中,选择“安装我从列表中手动选择的硬件(高级)”,然后单击“下一步”按钮。 | | five | Scroll down to highlight Network Adapter and click the Next button. See Figure 10-54.![img/492721_1_En_10_Fig54_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig54_HTML.jpg)图 10-54。设备管理器,添加网络适配器 | | six | When the hardware device driver window appears, first select Microsoft under Manufacturer, and then select Microsoft KM-TEST Loopback Adapter under Model on the right. Click the Next button one more time. See Figure 10-55.![img/492721_1_En_10_Fig55_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig55_HTML.jpg)图 10-55。设备管理器,添加 Microsoft 环回适配器 | | seven | 在下一个屏幕上,单击下一步按钮安装硬件驱动程序。 | | eight | When the driver installation is completed and the “Complete Add Hardware Wizard” message appears, click the Finish button and also close the Device Manager. See Figure 10-56.![img/492721_1_En_10_Fig56_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig56_HTML.jpg)图 10-56。设备管理器,完成 Microsoft 环回适配器安装 | | nine | At the bottom-left corner, right-click the Windows icon and then select the Run menu. Next, type in **ncpa.cpl** in the Run window. This will open the Network Connections window with network adapters. See Figure 10-57.![img/492721_1_En_10_Fig57_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig57_HTML.jpg)图 10-57。运行网络连接的快捷方式 | | Ten | Locate and right-click the Microsoft KM-TEST loopback adapter icon, right-click the icon, and select the Rename option. Now change the name from Ethernet 2 to Loopback. See Figure 10-58.![img/492721_1_En_10_Fig58_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig58_HTML.jpg)图 10-58。Windows 10,重命名 Microsoft 环回适配器 | | Eleven | Once again, right-click the loopback adapter and select Properties. See Figure 10-59.![img/492721_1_En_10_Fig59_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig59_HTML.jpg)图 10-59。Windows 10,更改 Microsoft 环回适配器属性 | | Twelve | Highlight Internet Protocol Version 4 (TCP/IP4) and then click the Properties button. See Figure 10-60.![img/492721_1_En_10_Fig60_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig60_HTML.jpg)图 10-60。Windows 10,选择 IPv4 属性 | | Thirteen | Give an IP address for testing purposes; in this example, the IP address of the 7.7.7.0/24 subnet is used. The loopback adapter’s address given is 7.7.7.1 with a subnet mask of 255.255.255.0\. Leave the default gateway address blank; also, there’s no need to fill in the DNS information. See Figure 10-61.![img/492721_1_En_10_Fig61_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig61_HTML.jpg)图 10-61。Windows 10,对环回适配器的 IP 地址进行硬编码 | | Fourteen | 单击确定按钮,关闭所有窗口和正在运行的应用,包括 GNS3。为了让 GNS3 识别新安装的 MS 环回接口,我们需要再次重启 PC。 | 当您的 PC 重新启动并正确引导时,让我们使用 Microsoft loopback 适配器创建一个新的 GNS3 项目,设置一个路由器,并将主机与虚拟 Cisco IOS 路由器连接起来。之后,相同的连接方法可以连接到其他思科 L2 和 L3 设备以及其他类型的 GNS3 虚拟设备。 ### 使用 MS 回环访问 GNS3 网络设备 创建一个基本的 GNS3 项目后,您将测试 R1 和主机 PC 之间的通信。图 10-62 显示了基本拓扑。 ![img/492721_1_En_10_Fig62_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig62_HTML.jpg) 图 10-62。 主机到 GNS3 路由器的通信 要配置简单的 Microsoft loopback 实验室,请遵循以下说明: | # | 工作 | | --- | --- | | one | Use the GNS3 icon on the desktop to start GNS3\. Wait until GNS3 VM starts and CPU settles down. See Figure 10-63.![img/492721_1_En_10_Fig63_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig63_HTML.jpg)图 10-63。GNS3,GNS3 桌面图标 | | Two | Now create a new project named `loopback_to_r1`. You do not have to follow this naming convention, so use your imagination to give a meaningful name. See Figure 10-64.![img/492721_1_En_10_Fig64_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig64_HTML.jpg)图 10-64。GNS3,创建新项目 loopback_to_r1 | | three | Now put together a simple lab topology. Open the “All devices” menu and drop one IOS router and one cloud onto the Topology canvas. R1 will run on GNS3 VM, and Cloud-1 will be running on the host PC. Look at the server’s summary for your reference. See Figure 10-65.![img/492721_1_En_10_Fig65_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig65_HTML.jpg)图 10-65。GNS3,创建拓扑 | | four | 右键单击 Cloud-1,选择“配置”,然后选择“显示特殊以太网接口”从下拉菜单中添加环回接口;删除以太网和任何其他接口,然后单击关闭按钮。If you do not see the Microsoft loopback interface, you probably did not reboot your PC or incorrectly added the cloud to the GNS3 VM. Suppose the GNS3 does not recognize the Microsoft loopback even after rebooting. In that case, there may be a problem with the operating system you are using, or possibly, there was a problem during the interface installation. If you run into a problem at this point, go back to the previous steps and correct the issue. See Figure 10-66.![img/492721_1_En_10_Fig66_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig66_HTML.jpg)图 10-66。GNS3,将环回接口添加到 Cloud-1 | | five | Right-click and rename Cloud-1 to Host-PC. Then connect the Loopback interface to R1s f0/0, as shown in Figure 10-67. Please note that if GNS3 throws an error and refuses to connect, you may need to change “User account control setting” on Windows 10.![img/492721_1_En_10_Fig67_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig67_HTML.jpg)图 10-67。GNS3,将 Microsoft 环回接口连接到 R1 的 f0/0 端口 | ![img/492721_1_En_10_Figh_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Figh_HTML.gif)如何更改用户账户控制设置? 从控制面板中,打开“用户帐户控制设置”,并将设置更改为“从不通知”这将允许您将 MS 环回接口与 GNS3 设备连接。完成此更改后,您必须再次重启电脑。参见图 10-68 和图 10-69 。 ![img/492721_1_En_10_Fig69_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig69_HTML.jpg) 图 10-69。 用户帐户控制设置,更改为“从不通知” ![img/492721_1_En_10_Fig68_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig68_HTML.jpg) 图 10-68。 用户帐户控制设置,默认 ![img/492721_1_En_10_Figi_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Figi_HTML.gif)如何打开 Windows Defender 防火墙允许 ICMP? 要允许 GNS3 的 R1 向主机发送 ICMP ( `ping`)数据包,您必须打开具有高级安全性的 Windows Defender 防火墙,并启用图 10-70 所示的入站规则设置。 ![img/492721_1_En_10_Fig70_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig70_HTML.jpg) 图 10-70。 Windows 10,打开具有高级安全性的 Windows Defender 防火墙 可以打开图 10-71 中的 Windows Defender 防火墙。 | # | 工作 | | --- | --- | | six | 打开 R1 的控制台,配置 R1 的 f0/0 接口,如下所示:`R1(config)#interface FastEthernet 0/0``R1(config-if)#ip address 7.7.7.2 255.255.255.0``R1(config-if)#no shut``R1(config-if)#end``R1#ping 7.7.7.1``Type escape sequence to abort.``Sending 5, 100-byte ICMP Echos to 7.7.7.1, timeout is 2 seconds:``.!!!!``Success rate is 80 percent (4/5), round-trip min/avg/max = 8/11/12 ms``R1#write memory``Building configuration...``OK` | | seven | 在主机笔记本电脑/PC 的命令行提示符下,ping R1 的 f0/0 接口 IP 地址 7.7.7.2 以检验通信。`C:\Users\brendan>ping 7.7.7.2``Pinging 7.7.7.2 with 32 bytes of data:``Reply from 7.7.7.2: bytes=32 time=22ms TTL=255``Reply from 7.7.7.2: bytes=32 time=6ms TTL=255``Reply from 7.7.7.2: bytes=32 time=8ms TTL=255``Reply from 7.7.7.2: bytes=32 time=11ms TTL=255``Ping statistics for 7.7.7.2:``Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),``Approximate round trip times in milli-seconds:``Minimum = 6ms, Maximum = 22ms, Average = 11ms` | | eight | Configure the username and Telnet on R1\. For testing purposes, Telnet is configured here : | `R1#configure terminal``R1(config)#username pynetauto privilege 15 secret cisco123``R1(config)#line vty 0 15``R1(config-line)#transport input telnet``R1(config-line)#login local``R1(config-line)#logging synchronous``R1(config-line)#end``R1#copy running-config startup-config` | Enter the configuration mode 【 A local user named `pynetauto`, Permission 15 and password `cisco123`Enter vty line modeEnable remote loginChange lineto return to execution modewhen receiving log messages with local user credentials. | | | nine | Using PuTTY, Telnet into R1 using port 23\. See Figure 10-72.![img/492721_1_En_10_Fig72_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig72_HTML.jpg)图 10-72。PuTTY,Telnet 进入 R1 | | Ten | 输入用户 ID 和密码,从主机(7.7.7.1)登录 7.7.7.2 R1。`User Access Verification``Username: pynetauto``Password: ********``R1#` | * 文件和打印机共享(回显请求–icmp v4-In)私有 * 文件和打印机共享(回显请求–icmp V6-In)私有 ![img/492721_1_En_10_Fig71_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig71_HTML.jpg) 图 10-71。 具有高级安全性的 Windows 10 Windows Defender 防火墙,支持文件和打印机共享以允许 ICMP 您已从主机成功连接到运行在 GNS3 上的 IOS 设备。现在,您可以在 Windows PC 上轻松配置 GNS3 路由器。 ## 从 Windows 主机 PC 使用 Python 脚本配置 GNS3 IOS 路由器 以下是一些 Python 实验室目标: * 找到 Python 资源并在学习中重用代码 * 远程登录到一台 Cisco IOS 设备 * 配置环回接口和 f0/1 现在,让我们在主机 PC 的 Windows 上创建一个简单的 Python Telnet 脚本,并配置一个环回接口和 FastEthernet 0/1 接口。即使您以前没有编写过有效的 Python 脚本,也只需跟随并完成本实验中的每项任务。转到任务中提到的 URL,向下滚动到网页的底部,找到一个 Python Telnet 示例。复制此示例,您将通过 Telnet 创建您的第一个 Cisco 路由器交互脚本。该脚本使用 Python 内置库连接到您的 GNS3 R1 路由器,使用`getpass`模块获取用户输入和密码。 Python 库是有用模块的集合。Python 模块可以被认为是为其他用户编写和测试的小程序,因此您不必每次都重新发明轮子。内置或标准库是在机器上安装 Python 时安装的库。外部或非标准库是根据需要安装在操作系统上的一组模块。 不要试图理解示例代码中的所有内容。我稍后会提供解释。试着把注意力集中在给定的任务上,密切注意打字错误。编写代码时,每个逗号、句号和空格都很重要。在 Python 脚本中,间距非常重要。 | # | 工作 | | --- | --- | | one | First, go to the Python installation folder, `C:\Python38`, and create a new folder to store your scripts. Here I have created a folder named `myscripts`. See Figure 10-73.![img/492721_1_En_10_Fig73_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig73_HTML.jpg)图 10-73。主机,创建新文件夹 | | Two | 这个脚本中使用的`getpass`和`telnet`库都是内置的(标准)Python 库,所以不需要单独安装它们。只需转到以下网址并跟随: [`https://docs.python.org/3/library/telnetlib.html`](https://docs.python.org/3/library/telnetlib.html) 。Telnet 示例这里有一个简单的例子来说明典型的用法:`import getpass``import telnetlib``HOST = "localhost"``user = input("Enter your remote account: ")``password = getpass.getpass()``tn = telnetlib.Telnet(HOST)``tn.read_until(b"login: ")``tn.write(user.encode('ascii') + b"\n")``if password:``tn.read_until(b"Password: ")``tn.write(password.encode('ascii') + b"\n")``tn.write(b"ls\n")``tn.write(b"exit\n")``print(tn.read_all().decode('ascii'))` | | three | 使用 Notepad++复制并粘贴前面的内容。将文件另存为 Windows PC 上的`C:\python38\myscripts`文件夹下的`telnet_script1.py`。按如下方式修改该文件;代码中修改的部分用粗体表示。完成后,将脚本保存为一个`.py`文件。这个脚本将在后面的章节中详细解释,但是现在,简单地一字不差地跟着实验。当你开始你的 Python 编码之旅时,你往往会从模仿或复制别人的工作中学到最多的东西;这和新生儿如何从父母和周围的人那里学习说话是类似的。`telnet_script1.py``import getpass``import telnetlib``HOST = "7.7.7.2" <<< Router ip``user = input("Enter your telnet username: ")``password = getpass.getpass()``tn = telnetlib.Telnet(HOST)``tn.read_until(b"Username: ") <<< Should be same as the router prompt, ensure to update this line.``tn.write(user.encode('ascii') + b"\n")``if password:``tn.read_until(b"Password: ")``tn.write(password.encode('ascii') + b"\n")``tn.write(b”conf t\n”)``tn.write(b”int loop 0\n”)``tn.write(b”ip add 1.1.1.1 255.255.255.255\n”)``tn.write(b”int f0/1\n”)``tn.write(b”ip add 20.20.20.1 255.255.255.0\n”)``tn.write(b”no shut\n”)``tn.write(b”end\n”)``tn.write(b”show ip int bri\n”)``tn.write(b”exit\n”)``print(tn.read_all().decode(‘ascii’))` | | four | 从 GNS3 打开 R1 的控制台,或者使用 PuTTY 远程登录到 7.7.7.2 的 R1,通过运行`show ip interface brief`检查当前的接口状态。我们确认在此阶段只配置了一个接口。`R1#show ip interface brief``Interface              IP-Address      OK? Method    Status                     Protocol``FastEthernet0/0        7.7.7.2         YES NVRAM     up                         up``FastEthernet0/1        unassigned      YES NVRAM     administratively down      down` | | five | 在 R1 的控制台上,启用 Telnet 身份验证调试,以确认身份验证和活动。当您运行脚本并进行身份验证时,保持 R1 的控制台窗口打开并观察日志。 | `R1# debug ip auth-proxy telnet` | 为远程登录身份验证启用调试 | | | six | 现在打开 Windows PowerShell,导航到`C:\Python38\myscripts\`文件夹,运行`python telnet_script1.py`。当提示输入用户名和密码时,使用在上一节中创建的凭据进行身份验证。对于密码,当你输入时,你不会看到任何字符,因为`getpass`模块会自动隐藏这些信息。`PS C:\Users\brendan> cd C:\Python38\myscripts\``PS C:\Python38\myscripts> python telnet_script1.py``Enter your telnet username: pynetauto``Password: ********``R1#conf t``Enter configuration commands, one per line.  End with CNTL/Z.``R1(config)#int loop 0``R1(config-if)#ip add 1.1.1.1 255.255.255.255``R1(config-if)#int f0/1``R1(config-if)#ip add 20.20.20.1 255.255.255.0``R1(config-if)#no shut``R1(config-if)#end``R1#show ip int bri``Interface                  IP-Address      OK? Method     Status               Protocol``FastEthernet0/0            7.7.7.2         YES NVRAM      up                   up``FastEthernet0/1            20.20.20.1      YES manual     up                   up``Loopback0                  1.1.1.1         YES manual     up                   up``R1#exit` | | seven | 现在回到 R1 的控制台,检查 Telnet 日志,并再次运行`show ip interface brief`命令。结果将类似于下图,其中`Loopback0`和`FastEthernet0/1`配置了新的 IP 地址,并显示为 up/up。`Telnet session logs during remote configuration``R1#``AUTH-PROXY Telnet debugging is on``R1#``*Mar  1 00:59:51.655: %SYS-5-CONFIG_I: Configured from console by pynetauto on vty0 (7.7.7.1)``*Mar  1 00:59:51.687: %LINEPROTO-5-UPDOWN: Line protocol on Interface Loopback0, changed state to up``R1#``*Mar  1 00:59:53.583: %LINK-3-UPDOWN: Interface FastEthernet0/1, changed state to up``*Mar  1 00:59:54.583: %LINEPROTO-5-UPDOWN: Line protocol on Interface FastEthernet0/1, changed state to up``R1#show ip interface brief``Interface                  IP-Address      OK? Method     Status                Protocol``FastEthernet0/0            7.7.7.2         YES NVRAM      up                    up``FastEthernet0/1            20.20.20.1      YES manual     up                    up``Loopback0                  1.1.1.1         YES manual     up                    up` | ## 思科 IOS 和 GNS3 设备实验室 GNS3 有许多有用的模板,您可以在学习网络、安全和系统概念时使用。您可以添加预配置的模板,将其作为设备安装在 GNS3 虚拟机上,并在您的实验室中使用。您甚至可以安装带有完整 GUI 界面的 Palo Alto 防火墙,并在实验室中使用它们。出于演示目的,您将导入一个简单的 Linux Docker 映像,并从 Linux Docker 映像执行一个交互式 Telnet 会话。 本实验的目标如下: * 学习安装 GNS3 装置设备 * 学习在 Linux 服务器以太网接口上手动分配 IP 地址 * 学习运行实时交互式远程登录会话来管理 Cisco IOS 设备 ### 导入和安装 GNS3 Linux 设备服务器 本实验上接上次环回实验。首先,让我们从 GNS3 模板添加一个 Linux Docker 设备,然后将其添加到现有拓扑中。 | # | 工作 | | --- | --- | | one | In the GNS3 main window, go to File ➤ + New template (Figure 11-74). Introduce all the remaining figures. Alternatively, you can open “All devices” and click “+New template” as well.![img/492721_1_En_10_Fig74_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig74_HTML.jpg)图 10-74。GNS3,+新模板 | | Two | 在“新建模板”向导窗口中,保留“从 GNS3 服务器安装设备(推荐)”选项,然后单击“下一步”按钮。 | | three | On the Appliances from the server screen, type in **Network Automation** or use the drop-down menu under Guests and select Network Automation Docker; then click the Install button. See Figure 10-75.![img/492721_1_En_10_Fig75_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig75_HTML.jpg)图 10-75。GNS3 新模板,选择装置 Linux 服务器 | | 为了方便起见,GNS3 上有许多预装的设备 Dockerized 和 Qemu 映像。网络自动化 Docker 镜像基本上是一个 Ubuntu 18 LTS 服务器 dockered 镜像,用于运行快速网络实验室。当 Docker 关闭电源时,安装的内容将默认为原始设置。Docker 不像 CentOS 或 Ubuntu VMs 那样是一个成熟的虚拟机,因为它是一个轻量级设备,依赖于托管服务器的内核来运行。在前一章中你会学到更多关于 Docker 的知识。 | | four | 在服务器屏幕上,单击下一步按钮进入下一屏幕。 | | five | 在下一个屏幕上,要完成并关闭“新建模板”窗口,请单击“完成”按钮。 | | six | Click the OK button to add the Network Automation Server template. See Figure 10-76.![img/492721_1_En_10_Fig76_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig76_HTML.jpg)图 10-76。GNS3 新模板,“添加模板”窗口 | | seven | Now go back to GNS3’s main user interface and select the “End devices” icon on the left. The “End devices” icon is the one that looks like a computer screen. You will see your new appliance device, the Network Automation server, on the list. Make sure your computer is connected to the Internet and select the icon. Then drag and drop the icon onto the Topology canvas. This action will initiate the Docker pull action and start downloading the Docker image from the Internet (DockerHub). See Figure 10-77.![img/492721_1_En_10_Fig77_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig77_HTML.jpg)图 10-77。GNS3,从 DockerHub 中拉出网络自动化设备 | | eight | Now connect eth0 of NetworkAutomation-1 server to R1’s f0/1 interface and power on the server. See Figure 10-78.![img/492721_1_En_10_Fig78_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig78_HTML.jpg)图 10-78。GNS3,将网络自动化-1 连接到 R1 | | nine | 一旦 Docker 服务器启动,通过运行`cat /etc/*release` Linux 命令检查操作系统版本。这个服务器是一个容器化的(Docker) Ubuntu 18.04 LTS 服务器镜像。`root@NetworkAutomation-1:~# cat /etc/*release``DISTRIB_ID=Ubuntu``DISTRIB_RELEASE=18.04``DISTRIB_CODENAME=bionic``DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"``NAME="Ubuntu"``VERSION="18.04.4 LTS (Bionic Beaver)"``ID=ubuntu``ID_LIKE=debian``PRETTY_NAME="Ubuntu 18.04.4 LTS"``VERSION_ID="18.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=bionic``UBUNTU_CODENAME=bionic` | 恭喜你!现在您有了另一个服务器,可以在那里练习 Python 脚本。稍后,您将了解更多关于如何创建 Docker 映像的信息。请记住,Docker 映像是一种临时服务器。当 Docker 机器断电时,一切都重置为默认值,您将丢失以前的工作。要保存工作,您可能需要将它保存到映射的驱动器,或者在它处于运行状态时捕获另一个 Docker 映像。这超出了我们的主题,但请花时间研究 Docker 和 Kubernetes 的概念,因为这是系统自动化领域的另一个热门话题。 ### 手动为 GNS3 Linux 设备服务器分配 IP 地址 这里,让我们练习为您在前面步骤中刚刚安装的 Linux 设备服务器分配 IP 地址、默认网关和 DNS 服务器。确保向默认网关发送 ping 命令,以确认通信正常。在下一部分中,您将连接到 Internet,因此不需要 ping 任何 Internet 地址。 | # | 工作 | | --- | --- | | one | 在您的 NetworkAutomation-1 Linux 服务器上运行以下命令,以分配一个新的 IP 地址,并添加一个默认网关和 Google 的公共名称服务器。同样的方法也可以用于在一个活动的 Linux 服务器上分配 IP 地址。`root@NetworkAutomation-1:~# ifconfig eth0 <<< Check eth0 interface``eth0: flags=4163  mtu 1500``inet6 fe80::2cbb:cdff:fe4e:96fc  prefixlen 64  scopeid 0x20``ether 2e:bb:cd:4e:96:fc  txqueuelen 1000  (Ethernet)``RX packets 105  bytes 10924 (10.9 KB)``RX errors 0  dropped 91  overruns 0  frame 0``TX packets 13  bytes 1006 (1.0 KB)``TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0``root@NetworkAutomation-1:~# ifconfig eth0 20.20.20.2 netmask 255.255.255.0 up <<< manually configure IP address and bring up eth0``root@NetworkAutomation-1:~# route add default gw 20.20.20.1 <<< Configure Gateway``root@NetworkAutomation-1:~# echo "nameserver 8.8.8.8" > /etc/resolve.conf <<< Configure DNS server``root@NetworkAutomation-1:~# ifconfig eth0 <<< Check configuration``eth0: flags=4163  mtu 1500``inet 20.20.20.2  netmask 255.255.255.0  broadcast 20.20.20.255``inet6 fe80::2cbb:cdff:fe4e:96fc  prefixlen 64  scopeid 0x20``ether 2e:bb:cd:4e:96:fc  txqueuelen 1000  (Ethernet)``RX packets 109  bytes 11470 (11.4 KB)``RX errors 0  dropped 94  overruns 0  frame 0``TX packets 13  bytes 1006 (1.0 KB)``TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0``root@NetworkAutomation-1:~# ping 20.20.20.1 -c 3 <<< test IP connectivity``PING 20.20.20.1 (20.20.20.1) 56(84) bytes of data.``64 bytes from 20.20.20.1: icmp_seq=1 ttl=255 time=30.8 ms``64 bytes from 20.20.20.1: icmp_seq=2 ttl=255 time=11.0 ms``64 bytes from 20.20.20.1: icmp_seq=3 ttl=255 time=6.54 ms``--- 20.20.20.1 ping statistics ---``3 packets transmitted, 3 received, 0% packet loss, time 2002ms``rtt min/avg/max/mdev = 6.546/16.167/30.877/10.565 ms` | ![img/492721_1_En_10_Figl_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Figl_HTML.gif)您还可以通过右键单击 NetworkAutomation-1 图标并选择“编辑配置”选项来配置设备的网络接口。您可以在这里手动配置它,或者简单地删除`#`键,从 DHCP 服务器获得一个有效的 IP 地址。我们没有在 20.20.20.0/24 网络上设置 DHCP 服务,所以我们选择手动配置 IP 地址。参见图 10-79 。 ![img/492721_1_En_10_Fig79_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_10_Fig79_HTML.jpg) 图 10-79。 网络自动化-1,编辑配置文件 ### 使用 GNS3 设备 Linux 的 Python 管理 R1 在本实验中,您将使用安装在 GNS3 设备 Linux 服务器上的 Python 从实时 Telnet 会话管理 R1。本实验上接上一节。 | # | 工作 | | --- | --- | | one | 首先,使用图 10-79 中所示的命令检查该服务器上预装了哪些 Python 版本。如果你下载了和这本书一样的 Dockerized Linux 服务器,它会预装 Python 2.7.17 和 3.8.3。`root@NetworkAutomation-1:~# which python``/usr/bin/python``root@NetworkAutomation-1:~# python --version``Python 2.7.17``root@NetworkAutomation-1:~# which python3``/usr/bin/python3``root@NetworkAutomation-1:~# python3 -V``Python 3.8.3` | | Two | 现在通过键入`python3`开始 Python 3 交互式会话。`root@NetworkAutomation-1:~# python3``Python 3.8.3 (default, May 14 2020, 20:11:43)``GCC 7.5.0 on linux``Type "help", "copyright", "credits" or "license" for more information.``>>>` | | three | 现在,在 GNS3 设备 Linux 服务器的 Python 解释器窗口中,逐字键入以下 Python 命令。不要太担心这些命令的深层含义;现在,试着熟悉环境中的流程。在这里,您通过 Telnet 会话将 loopback 5 添加到 R1,然后打印会话日志。启动 Telnet 会话后,您为 loopback 5 配置发送多个`tn.write`命令。然后,您必须通过发送`tn.write(b"exit\n")`命令关闭 Telnet 会话来退出会话;然后,您可以在 Telnet 会话中打印它们。`>>> import getpass <<< Import getpass module``>>> import telnetlib << Import telnetlib module``>>> tn = telnetlib.Telnet("20.20.20.1") <<< Create a telnet session to 20.20.20.1``>>> tn.write("pynetauto".encode('ascii') + b"\n") <<< Send username 'pynetauto' to R1``>>> tn.write("cisco123".encode('ascii') + b"\n") <<< Send password 'cisco123' to R1``>>> tn.write(b"configure terminal\n") <<< Enter config mode``>>> tn.write(b"interface loopback 5\n")``>>> tn.write(b"ip address 5.5.5.5 255.255.255.255\n")``>>> tn.write(b"end\n")``>>> tn.write(b"exit\n")``>>> print(tn.read_all().decode('ascii')) <<< Print all session logs in tn`在一个成功的交互式会话中,输出将类似于下面这样。注意每一个逗号、句号和空格,也要避免任何打字错误。Python 编码并不像某些 Python 布道者宣扬的那样光鲜亮丽;对于每个逗号、句号、空格和每个字符,您的指尖都必须接触键盘。不要买蛇油。`User Access Verification``Username: pynetauto``Password:``R1#configure terminal``Enter configuration commands, one per line.  End with CNTL/Z.``R1(config)#interface loopback 5``R1(config-if)#ip address 5.5.5.5 255.255.255.255``R1(config-if)#end``R1#exit` | | four | 现在继续使用`show ip interface brief`命令并打印出日志。请注意,您不必再次导入模块,因为您仍处于同一会话中。路由器命令前的`b`表示双引号中的命令以字节形式发送,`\n`代表换行符,在这种情况下意味着按下键盘上的 Enter 键一次。`>>> tn = telnetlib.Telnet("20.20.20.1") <<< Create a telnet session to 20.20.20.1``>>> tn.write("pynetauto".encode('ascii') + b"\n") <<< Send username ‘pynetauto’ to R1``>>> tn.write("cisco123".encode('ascii') + b"\n") <<< Send password ‘cisco123’ to R1``>>> tn.write(b"show ip interface brief\n") <<< Send ‘show ip int brief’ command``>>> tn.write(b"exit\n") <<< Send ‘exit’ command``>>> print(tn.read_all().decode('ascii')) <<< Print all session logs in tn`完成实验任务后,您将看到在 R1 上运行`show ip interface brief`命令的结果。`User Access Verification``Username: pynetauto``Password:``R1#show ip interface brief``Interface                  IP-Address      OK? Method Status                Protocol``FastEthernet0/0            7.7.7.2         YES NVRAM  up                    up``FastEthernet0/1            20.20.20.1      YES manual up                    up``FastEthernet1/0            unassigned      YES NVRAM  administratively down down``Loopback0                  1.1.1.1         YES manual up                    up``Loopback5                  5.5.5.5         YES manual up                    up``R1#exit` | 您已经使用 Python 3 完成了交互式 Telnet 会话。当您开发代码来验证 Python 解释器或与网络设备的交互会话时,您可以测试部分 Python 代码。现在,将相同的命令复制并粘贴到您选择的文本编辑器中,并为文件名指定一个`.py`文件扩展名。本章使用的 Python 脚本保存为`chapter6_codes`,可以在 [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) 下载。 ## 摘要 在本章中,您学习了许多 GNS3 技术,这些技术将帮助您成功完成本书中的其余实验。您学习了如何安装 GNS3、如何构建简单的 GNS3 拓扑、如何在 GNS3 上集成 Cisco IOS 路由器、如何安装 GNS3 设备服务器来管理运行在 GNS3 上的 IOS 路由器,以及如何安装 Microsoft loopback 接口来实现 GNS3 设备与主机 PC 之间的通信。此外,您还学习了如何将 GNS3 路由器连接到 GNS3 的设备 Linux Python 服务器。在第十一章中,我们将创建一个新的 GNS3 项目,通过 Telnet 使用 IOS 路由器 R1 与 Ubuntu 和 CentOS 虚拟服务器进行通信。我们将扩展我们在本章中学到的知识,并获得足够的实践来向前迈进。 # 十一、思科 IOS 实验室 这将是本书中最短的章节之一。您将测试您的 Linux 虚拟机(VMs ),通过 Telnet 与 IOS 路由器 R1 交互。您将学习创建一个新的 GNS3 项目,使用简单的 Cisco 配置运行一个快速文件传输测试实验室,最后克隆一个 GNS3 项目。您正在逐步走向 Python 网络自动化。 ![img/492721_1_En_11_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Figa_HTML.jpg) ## 思科 IOS 和 Linux 虚拟机实验室 在本实验中,您将把 Linux 虚拟机连接到 GNS3 设备。在大多数生产环境中,在 Linux 平台上运行 Python 是获得在企业环境中使用 Linux 的众多优势的标准。因此,仅仅理解如何编写 Python 代码不足以创建一个有用的应用来自动化和简化网络工程师的任务。你需要对整个 IT 生态系统有更深入的了解,知道不同的系统和技术如何在一个 IT 生态系统中和谐共存。你必须愿意走出你的舒适区,投入个人努力去理解 IT 生态系统中的其他技术。 在这个 Cisco IOS 和 Linux VM 示例中,我们将配置一个新拓扑,为下一章做准备。图 11-1 显示了您将要配置的拓扑。请注意,CentOS 8.1 和 Ubuntu 20 LTS Linux 服务器已经连接到 VMware 工作站上的 VMnet8,如图所示。在 GNS3 中,我们将使用代表性图标来标记它们位于 192.168.130.0/24 子网中。如果您对 VMnet8 使用不同的 IP 子网,您的 IP 地址将会不同。 该拓扑使用运行在主机 PC 上的 NAT-1,连接到 VMware 工作站的 VMnet8 它还充当 DHCP 服务器和连接到互联网的网关。IP 地址为 7.7.7.1 的主机 PC 是带有 Microsoft 环回接口的云,该接口连接到 7.7.7.0/24 网络上 R1 的 F0/1。 ![img/492721_1_En_11_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig1_HTML.jpg) 图 11-1。 linuxvm2ios 实验室拓扑 ## 为 Linux 虚拟机实验室创建新的 GNS3 项目 让我们创建一个新项目,将 Linux VM 服务器连接到一个新的 IOS 路由器。创建新项目的过程类似于第十章中之前的项目。让我们创建一个新项目,将我们的 Linux 虚拟机连接到一个 IOS 路由器。 | **#** | **任务** | | --- | --- | | **1** | 从 GNS3 主菜单中,选择文件➤新建空白项目以打开新项目窗口。 | | **2** | Give your new project a new name. The new project name is `linuxvm2ios` for this lab. You do not have to follow the same naming convention. Feel free to give your project a unique and meaningful name. See Figure 11-2.![img/492721_1_En_11_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig2_HTML.jpg)图 11-2。linuxvm2ios 项目,创建一个新的 GNS3 项目 | | **3** | 如图 11-3 所示配置 GNS3 拓扑。构建新拓扑时,请参考 GNS3 的拓扑和服务器摘要窗口了解更多信息。您将能够看到每个设备是如何连接的。连接所有设备后,启动 R1 和 PC-1。以下几点是在 GNS3 画布上连接和配置拓扑时的连接参考:在主机服务器上运行 NAT-1。(使用工作站的 VMnet8 中的 NAT。)Ethernetswitch-1 安装在 GNS3 VM 服务器上,仅用作连接多个设备的虚拟交换机。PC-1 是安装在 GNS3 上的 VPCS。VPCS 使得测试设备之间的通信变得容易。这类似于在路由器上配置虚拟环回接口。标有 Microsoft loopback 的主机 PC 是 cloud-1,只是为了美观而改变了图标。     •   Lastly, the CentOS 8.1 and Ubuntu 20.4 LTS servers are directly connected to VMnet8 on VMware Workstation and will not show up on the GNS3 topology. These server icons are also for aesthetic representation.![img/492721_1_En_11_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig3_HTML.jpg)图 11-3。linuxvm2ios 项目,GNS3 拓扑连接 | |   | Note that in the topology in Figure 11-3, when you drag and drop NAT-1 and Host-PC (cloud-1), the network of VMnet8 should be used, so the host PC (pynetauto) should be selected. Your PC name will be different. See Figure 11-4.![img/492721_1_En_11_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig4_HTML.jpg)图 11-4。linuxvm2ios 项目,为 NAT-1 和 cloud-1 选择主机服务器 | |   | Use the Servers Summary window shown in Figure 11-5 as a reference point while configuring and running the GNS3 topology.![img/492721_1_En_11_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig5_HTML.jpg)图 11-5。linuxvm2ios 项目,服务器摘要窗口 | |   | When you connect Ethernetswitch-1, a VPCS (PC-1), and an IOS router (R1), select GNS3 VM as the server, as shown in Figure 11-6.![img/492721_1_En_11_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig6_HTML.jpg)图 11-6。linuxvm2ios 项目,为所有其他设备选择 GNS3 VM 服务器 | |   | Refer to the Topology Summary window shown in Figure 11-7 to connect each device.![img/492721_1_En_11_Fig7_HTML.png](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig7_HTML.png)图 11-7。linuxvm2ios 项目,拓扑总结 | | **4** | Next, go to VMware Workstation’s main user window and start the CentOS 8.1 VM. When the server boots up, log in as the `pynetauto` user or your username. See Figure 11-8.![img/492721_1_En_11_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig8_HTML.jpg)图 11-8。CentOS 8.1,登录屏幕 | | **5** | 在 CentOS 8.1 上打开一个终端,首先检查这个服务器的 IP 地址。在终端上使用`ifconfig ens160`命令获取或确认您的服务器的 IP 地址。您也可以通过 SSH 连接到 CentOS 8.1 服务器。在本例中,应该为 192.168.183.0/24 子网分配一个 IP 地址,但是如果您为 VMnet8 使用不同的子网,应该会收到不同的 IP 地址。在本例中,IP 地址 192.168.183.130 已分配给以太网 160 接口。根据您的虚拟机子网,您的 IP 地址分配可能会有所不同。`[pynetauto@centos8s1 ~]$` `ifconfig ens160``ens160: flags=4163  mtu 1500``inet 192.168.183.130  netmask 255.255.255.0  broadcast 192.168.183.255``inet6 fe80::f49c:7ff4:fb3f:bf32  prefixlen 64  scopeid 0x20``ether 00:0c:29:85:a6:36  txqueuelen 1000  (Ethernet)``RX packets 87  bytes 10302 (10.0 KiB)``RX errors 0  dropped 0  overruns 0  frame 0``TX packets 118  bytes 15005 (14.6 KiB)``TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0` | | **6** | 还是在 VMware 工作站上,启动 Ubuntu 20 LTS 服务器,用同样的方式找出这个服务器的 IP 地址。发出`ifconfig ens33`或`ifconfig`或`ip address`命令。IP 地址 192.168.183.132 已经分配给`ens33`接口。根据您的虚拟机子网,您的 IP 地址分配可能会有所不同。pynetato @ Ubuntu 20s 1:~ $`ifconfig ens33``ens33: flags=4163  mtu 1500``inet 192.168.183.132  netmask 255.255.255.0  broadcast 192.168.183.255``inet6 fe80::20c:29ff:fef3:877c  prefixlen 64  scopeid 0x20``ether 00:0c:29:f3:87:7c  txqueuelen 1000  (Ethernet)``RX packets 222  bytes 18369 (18.3 KB)``RX errors 0  dropped 0  overruns 0  frame 0``TX packets 442  bytes 41326 (41.3 KB)``TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0` | | **7** | 现在在 GNS3 上,启动 R1 并等待 1 分钟。打开 R1 控制台,配置 IP 域查找和 FastEthernet 0/0 接口,从 VMnet8 的 DHCP 服务器获取 IP 地址。另外,将接口 FastEthernet 0/1 的 IP 地址配置为 7.7.7.2 255 . 255 . 255 . 0;此接口连接到主机 PC 的 Microsoft 环回接口。`R1(config)#` `ip domain lookup``R1(config)#` `interface FastEthernet0/0``R1(config-if)#` `ip address dhcp``R1(config-if)#` `no shut``R1(config-if)#``*Mar  1 00:01:15.543: %LINK-3-UPDOWN: Interface FastEthernet0/0, changed state to up``*Mar  1 00:01:16.543: %LINEPROTO-5-UPDOWN: Line protocol on Interface FastEthernet0/0, changed state to up``*Mar  1 00:01:26.003: %DHCP-6-ADDRESS_ASSIGN: Interface FastEthernet0/0` `assigned DHCP address 192.168.183.133, mask 255.255.255.0, hostname R1``# Confirm the interface status by running "show ip interface brief"``R1#` `show ip interface brief``Interface                  IP-Address      OK? Method Status                Protocol``FastEthernet0/0            192.168.183.133 YES DHCP   up                    up``FastEthernet0/1            unassigned      YES unset  administratively down down``FastEthernet1/0            unassigned      YES unset  administratively down down` | |   | 现在将接口`f0/1`配置为连接到微软环回接口并 ping 7.7.7.1。R1# `configure terminal``Enter configuration commands, one per line.  End with CNTL/Z.``R1(config)#` `interface FastEthernet 0/1``R1(config-if)#` `ip address 7.7.7.2 255.255.255.0``R1(config-if)#` `no shut``R1(config-if)#` `exit``R1#``*Mar  1 00:47:12.911: %SYS-5-CONFIG_I: Configured from console by console``R1#` `ping 7.7.7.1``Type escape sequence to abort.``Sending 5, 100-byte ICMP Echos to 7.7.7.1, timeout is 2 seconds:``.!!!!``Success rate is 80 percent (4/5), round-trip min/avg/max = 8/12/20 ms` | | **8** | 从 R1 的控制台 ping NAT-1 网关(192.168.183.2)、Centos 8.1 服务器(192.168.183.130)和 Ubuntu 20 LTS 服务器(192.168.183.132)。如果收到了所有的 ICMP 响应,那么您就在正确的轨道上。如前所述,如果您看到第一个数据包被丢弃,这是预期的正常 ARP 行为。`R1#` `ping 192.168.183.2``Type escape sequence to abort.``Sending 5, 100-byte ICMP Echos to 192.168.183.1, timeout is 2 seconds:``.!!!!``Success rate is 80 percent (4/5), round-trip min/avg/max = 8/13/24 ms``R1#` `ping 192.168.183.130``Type escape sequence to abort.``Sending 5, 100-byte ICMP Echos to 192.168.183.130, timeout is 2 seconds:``.!!!!``Success rate is 80 percent (4/5), round-trip min/avg/max = 8/13/20 ms``R1#` `ping 192.168.183.132``Type escape sequence to abort.``Sending 5, 100-byte ICMP Echos to 192.168.183.132, timeout is 2 seconds:``.!!!!` | | **9** | 类似地,在 GNS3 上启动 PC-1 (VPCS ),运行`ip dhcp`命令从 VMnet8 DHCP 服务器接收 IP 地址。使用`show ip`检查 IP 配置。`PC1>` `ip dhcp``DDORA IP 192.168.183.129/24 GW 192.168.183.2``PC1>` `show ip``NAME        : PC1[1]``IP/MASK     : 192.168.183.129/24``GATEWAY     : 192.168.183.2``DNS         : 192.168.183.2``DHCP SERVER : 192.168.183.254``DHCP LEASE  : 1719, 1800/900/1575``DOMAIN NAME : localdomain``MAC         : 00:50:79:66:68:00``LPORT       : 20010``RHOST:PORT  : 127.0.0.1:20011``MTU:        : 1500`PC1 的接口收到了 IP 地址 192.168.183.129。DDORA 不是一个儿童卡通人物的名字(*爱探险的朵拉*)。在这里,DORA 代表发现、提供、请求、确认,这是在服务器和客户端之间通过 DHCP 服务分配 IP 地址时使用的通信消息。 | | **10** | Finally, from your host PC’s Windows desktop, SSH into both Linux servers using PuTTY and perform similar ICMP tests. Enter the IP address of each server and save the sessions for easier access, as shown in Figure 11-9.![img/492721_1_En_11_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig9_HTML.jpg)图 11-9。PuTTY,为了方便起见保存 Linux 会话 | |   | 如果没有通信问题,您现在可以在 Linux 服务器上编写一些 Python 脚本,并与 Cisco IOS 路由器通信。从你的 Linux 服务器,发送 ICMP 消息到 R1 的`f0/0`界面进行快速验证。`[pynetauto@centos8s1 ~]$` `ping 192.168.183.133 -c 3``PING 192.168.183.133 (192.168.183.133) 56(84) bytes of data.``64 bytes from 192.168.183.133: icmp_seq=1 ttl=64 time=0.547 ms``64 bytes from 192.168.183.133: icmp_seq=2 ttl=64 time=0.318 ms``64 bytes from 192.168.183.133: icmp_seq=3 ttl=64 time=0.372 ms``--- 192.168.183.133 ping statistics ---``3 packets transmitted, 3 received, 0% packet loss, time 44ms``rtt min/avg/max/mdev = 0.318/0.412/0.547/0.099 ms``pynetauto@ubuntu20s1:~$` `ping 192.168.183.133 -c 3``PING 192.168.183.133 (192.168.183.133) 56(84) bytes of data.``64 bytes from 192.168.183.133: icmp_seq=1 ttl=255 time=5.92 ms``64 bytes from 192.168.183.133: icmp_seq=2 ttl=255 time=6.85 ms``64 bytes from 192.168.183.133: icmp_seq=3 ttl=255 time=5.70 ms``--- 192.168.183.133 ping statistics ---``3 packets transmitted, 3 received, 0% packet loss, time 2004ms``rtt min/avg/max/mdev = 5.699/6.158/6.851/0.498 ms` | | **11** | 在 R1 上,配置具有 15 级权限的用户名`enable password`,并启用 Telnet。`R1#` `configure terminal``R1(config)#` `enable password cisco123``R1(config)#` `username pynetauto privilege 15 password cisco123``R1(config)#` `line vty 0 15``R1(config-line)#` `login local``R1(config-line)#` `transport input telnet``R1(config-line)#` `no exec-timeout``R1(config-line)#` `end``R1#` `write memory`您可以从路由器本身测试 Telnet 设置,因此在 R1 上,运行`telnet 192.168.183.133`命令。同样,使用`show user`命令来检查登录的用户。`R1#` `telnet 192.168.183.133``Trying 192.168.183.133 ... Open``User Access Verification``Username:` `pynetauto``Password:``R1#` `show user``Line       User       Host(s)              Idle       Location``0 con 0                192.168.183.133      00:00:00``* 98 vty 0     pynetauto  idle                 00:00:00 192.168.183.133``Interface    User               Mode         Idle     Peer Address` | | **12** | 从您的`ubuntu20s1` Linux 服务器,使用 PuTTY 远程登录到 R1 的`f0/0`接口(192.168.183.133)。您分配的 IP 地址可能不同,因此请用您的 IP 地址替换 IP 地址。运行`show user`命令来检查哪些用户已经登录以及用于 Telnet 会话的线路。星号(`*`)表示来自活动连接的访问信息。`pynetauto@ubuntu20s1:~$` `telnet 192.168.183.133``Trying 192.168.183.133...``Connected to 192.168.183.133.``Escape character is '^]'.``User Access Verification``Username: pynetauto``Password:``R1#` `show user``Line       User       Host(s)              Idle       Location``0 con 0                192.168.183.133      00:05:07``98 vty 0     pynetauto  idle                 00:05:07 192.168.183.133``* 99 vty 1     pynetauto  idle                 00:00:00 192.168.183.132``Interface    User               Mode         Idle     Peer Address`请注意,如果您使用 CentOS 远程登录到 R1,那么您可能需要在连接到互联网后首先安装 Telnet 客户端。`[pynetauto@centos8s1 ~]$` `sudo yum -y install telnet`或者`[pynetauto@centos8s1 ~]$` `sudo dnf -y install telnet` | | **13** | 现在,从您的 Windows 主机上,打开另一个 PuTTY 会话,这次通过`f0/1` (7.7.7.2)登录到 R1。`*`表示来自当前连接的访问信息。让我们运行`write memory`来保存 R1 的当前配置。`R1#` `show user``Line       User       Host(s)              Idle       Location``0 con 0                idle                 00:05:43``98 vty 0     pynetauto  idle                 00:00:40 192.168.183.133``99 vty 1     pynetauto  idle                 00:01:10 192.168.183.132``*100 vty 2     pynetauto  idle                 00:00:00 7.7.7.1``Interface    User               Mode         Idle     Peer Address``R1#` `write memory` | 现在,您已经完成了所有设备的通信和 Telnet 测试。现在,您已经准备好处理另一个有趣的 IOS 实验室了。 ## 从 Linux 虚拟机向 GNS3 IOS 路由器上传和下载文件(文件传输测试实验室) 在本实验中,您将向 GNS3 的 IOS 路由器 R1 添加闪存,并从 TFTP 服务器(CentOS8.1)上传和下载文件。您将从 Ubuntu 20 LTS 服务器编写并运行该脚本。 编写一个 Python 脚本来完成以下任务: | **#** | **任务** | | --- | --- | | **1** | 在 GNS3 上,创建 Cisco 3725 IOS 路由器模板时,您已经向 Cisco 3725 的 PCMCIA disk0 添加了 64MB。If your router was not configured with a flash drive, first save the work and shut down the router. Then right-click, choose Configure, and move to the “Memories and disks” tab of R1\. Add 64MB, click Apply, and then click the OK button. Once the flash memory has been configured, start R1 again. See Figure 11-10.![img/492721_1_En_11_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig10_HTML.jpg)图 11-10。R1,检查闪存分配 | | **2** | 还记得在 8.3.3 中,我们在 CentOS8.1 上配置了一个 TFTP 服务器吗?转到 CentOS8.1 的 PuTTY 会话,现在确认 TFTP 服务的状态。TFTP 是在 CentOS8.1 Linux 上安装 IP 服务时配置的。Use the following commands to check for TFTP services running on CentOS8.1. | `sudo systemctl status tftp``sudo systemctl enable tftp``sudo systemctl start tftp` | 检查 TFTP 服务状态在系统启动时启用 TFTP 服务启动 TFTP 服务 | 如果 CentOS 8.1 上的 TFTP 服务运行正常,它应该以绿色显示活动(正在运行)。如果 TFTP 有问题,请使用前面显示的`enable`和`start`命令来启动该服务器上的服务。如果 CentOS 询问`sudo`用户密码,请输入您的密码。在 4.5.3 中,我们为 TFTP 服务配置了一个名为`/var/tftpdir`的默认文件夹,以便在该文件夹中共享文件。`[pynetauto@localhost ~]$` `sudo systemctl enable tftp``[sudo] password for pynetauto: **********``[pynetauto@localhost ~]$` `sudo systemctl start tftp``[pynetauto@localhost ~]$` `sudo systemctl status tftp``● tftp.service - Tftp Server` | |   | `Loaded: loaded (/usr/lib/systemd/system/tftp.service; indirect; vendor prese>``Active: active (running) since Fri 2021-01-08 23:43:05 AEDT; 4s ago``Docs: man:in.tftpd``Main PID: 4237 (in.tftpd)``Tasks: 1 (limit: 11166)``Memory: 328.0K``CGroup: /system.slice/tftp.service``└─4237 /usr/sbin/in.tftpd -s /var/lib/tftpboot``Jan 08 23:43:05 localhost systemd[1]: Started Tftp Server.``lines 1-11/11 (END)` | | **3** | 在你的 Ubuntu 服务器(主要 Python 服务器)上,重用第十章中`telnet_script1`的 Telnet 示例代码,让我们重写 Telnet 脚本来备份 R1 的运行配置。我将把脚本重命名为`backup_config.py`并修改代码。被修改的代码部分被突出显示。转到 Ubuntu 20 LTS Python 服务器,使用 nano 文本编辑器创建以下代码:`pynetauto@ubuntu20s1:~$` `pwd``/home/pynetauto``pynetauto@ubuntu20s1:~$` `mkdir ch11``pynetauto@ubuntu20s1:~$` `cd ch11``pynetauto@ubuntu20s1:~/ch11$` `nano backup_config.py``pynetauto@ubuntu20s1:~/ch11$` `ls``backup_config.py``backup_config.py``import getpass``import telnetlib``HOST = "192.168.183.133"``user = input(``"Enter your telnet username: "``password = getpass.getpass()``tn = telnetlib.Telnet(HOST)``tn.read_until(b"Username: ")``tn.write(user.encode('ascii') + b"\n")``if password` `:``tn.read_until(b"Password: ")``tn.write(password.encode('ascii') + b"\n")``tn.write(b"copy running-config tftp://192.168.183.130/running-config\n")` `#<<< Copy command``tn.write(b"\n")` `#<<< Enter key action``tn.write(b"\n")` `#<<< Enter key action``tn.write(b"exit\n")` `#<<< end session``print(tn.read_all().decode('ascii'))` | | **4** | 在运行前面的脚本之前,转到 CentOS8s1 并确认`/var/tftpdir`目录中的文件。你可以看到 8.3.3 中只有两个传输测试文件。`[pynetauto@centos8s1 ~]$` `pwd``/home/pynetauto``[pynetauto@centos8s1 ~]$` `cd /var/tftpdir``[pynetauto@centos8s1 tftpdir]$` `ls``transfer_file01                           transfer_file77` | | **5** | 现在从 Ubuntu 20 LTS Python 服务器,运行`python3 backup_config.py`命令。按照提示操作并检查返回的输出。输入您的网络管理员 ID 和密码来运行脚本。当运行配置被复制到 TFTP 服务器时,您应该会看到类似以下内容的消息:`pynetauto@ubuntu20s1:~/ch11$` `python3 backup_config.py``Enter your telnet username:` `pynetauto``Password: *********``R1#copy running-config tftp://192.168.183.130/running-config``Address or name of remote host [192.168.183.130]?``Destination filename [running-config]?``!!``1155 bytes copied in 2.168 secs (533 bytes/sec)``R1#exit` | | **6** | 现在回到 CentOS8s1 服务器,重新检查目录(`/var/tftpdir`)。您现在应该看到`running-config`文件保存在 TFTP 服务器上。`[pynetauto@centos8s1 tftpdir]$` `ls``running-config             transfer_file01                           transfer_file77` | | **7** | 现在,要将 IOS 映像上传到 GNS3 的 IOS 路由器的闪存,首先必须格式化闪存。打开 R1 的控制台,按照以下说明操作:`R1#` `show flash``No files on device``134211584 bytes available (2198889009152 bytes used)``R1#``*Mar  1 02:53:58.163: %PCMCIAFS-5-DIBERR: PCMCIA disk 0 is formatted from a different router or PC. A format in this router is required before an image can be booted from this device``R1#` `show file system` | |   | `File Systems:``Size(b)             Free(b)      Type       Flags  Prefixes``-                   -            opaque     rw     archive:``-                   -            opaque     rw     system:``-                   -            opaque     rw     tmpsys:``57336               54057        nvram      rw     nvram:``-                   -            opaque     rw     null:``-                   -            network    rw     tftp:``* 2199023220736     134211584    disk       rw     flash:``-                   -            flash      rw     slot0:``-                   -            opaque     wo     syslog:``-                   -            opaque     rw     xmodem:``-                   -            opaque     rw     ymodem:``-                   -            network    rw     rcp:``-                   -            network    rw     pram:``-                   -            network    rw     ftp:``-                   -            network    rw     http:``-                   -            network    rw     scp:``-                   -            opaque     ro     tar:``-                   -            network    rw     https:``-                   -            opaque     ro     cns:``R1#``format flash``Format operation may take a while. Continue? [``confirm``Format operation will destroy all data in "flash:".  Continue? [``confirm``Writing Monlib sectors....``Monlib write complete``Format: All system sectors written. OK...``Format: Total sectors in formatted partition: 131040``Format: Total bytes in formatted partition: 67092480``Format: Operation completed successfully.``Format of flash: complete``R1#``show flash``No files on device``66936832 bytes available (0 bytes used)` | | **8** | 您可以使用 Windows 或 WinSCP 的 FileZilla 客户端上传任何 IOS 文件或任何使用 Python `telnetlib`模块进行文件传输的文件。如果您的 Windows 主机 PC 上没有 WinSCP,请转到以下下载站点并安装一个:URL:[`https://winscp.net/eng/download.php`](https://winscp.net/eng/download.php)(wincp 下载)网址:[`https://filezilla-project.org/download.php?platform=win64`](https://filezilla-project.org/download.php%253Fplatform%253Dwin64)(Windows 版 FileZilla 客户端)安装 WinSCP 并启动应用后,使用以下设置通过端口 22 (SSH)连接到 CentOS8.1 服务器。参见图 11-11 。文件协议: **SCP**主机名: **192.168.183.130(您的 CentOS 8.1 IP 地址)**用户名: **pynetauto(您的 CentOS 8.1 用户名)**密码: ************(你的 CentOS 8.1 密码)** | |   | ![img/492721_1_En_11_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig11_HTML.jpg)图 11-11。WinSCP,连接到 TFTP 服务器(CentOS8.1) | | **9** | When you accept the SSH key and log in, drill down to the `/var/tftpdir/` directory of your TFTP (CentOS8.1) server and drag and drop an IOS file for file sharing. You can also use another file in place of the IOS file to save time as you will be testing the file transfer concept only using Python’s `telnetlib` module. In this example, I am using the old IOS file named `c3725-adventerprisek9-mz.124-15.T14.bin`, but you can choose to use any file here for this test. See Figure 11-12.![img/492721_1_En_11_Fig12_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig12_HTML.jpg)图 11-12。WinSCP,复制 IOS 或任何文件传输测试文件 | | **10** | 在 R1 上,闪存驱动器已经在前面的步骤中格式化,没有必要检查闪存上是否存在任何文件。 | | **11** | 现在在 Ubuntu 20 LTS 服务器上,复制`backup_config.py`并保存为`upload_ios.py`。`pynetauto@ubuntu20s1:~/ch11$` `ls``backup_config.py``pynetauto@ubuntu20s1:~/ch11$` `cp backup_config.py upload_ios.py``pynetauto@ubuntu20s1:~/ch11$` `nano upload_ios.py`然后修改 Python 代码,使其看起来像下面的代码。更新后的零件会突出显示。您可以从头开始输入脚本,但是重用之前的脚本并进行必要的修改会更容易。此外,可以从官方下载站点下载该脚本。 | |   | `upload_ios.py``import getpass``import telnetlib``HOST = "192.168.183.133"``user = input(``"Enter your telnet username: "``password = getpass.getpass()``tn = telnetlib.Telnet(HOST)``tn.read_until(b``"Username: "``tn.write(user.encode('ascii') + b"\n")``if password` `:``tn.read_until(b"Password: ")``tn.write(password.encode('ascii') + b"\n")``tn.write``(``b"``tn.write(b”\n”)` `#<<< Enter key action``tn.write(b”exit\n”)` `#<<< end session``print(tn.read_all().decode('ascii'))` | | **12** | 运行 Python 代码。看起来应用挂起了,但实际上,它正在将文件从 TFTP 服务器传输到 R1 的闪存中。TFTP 是一种慢速协议,传输这个 45MB 的文件需要很长时间。在您的实践中,您可以创建任何较小的虚拟`.txt`文件以节省时间。`pynetauto@ubuntu20s1:~/ch11$` `python3 upload_ios.py``Enter your telnet username:` `pynetauto``Password: ********`| | | **13** | 运行前面的脚本后,返回 R1 的控制台,使用`show users`命令进行远程登录的 vty 会话,并使用`dir`或`show flash:`命令检查文件传输是否正在进行。`R1#``show flash``-#- --length-- -----date/time------ path``1      1470464 Mar 01 2002 01:02:28 c3725-adventerprisek9-mz.124-15.T14.bin``65466368 bytes available (1470464 bytes used)``R1#``show flash``-#- --length-- -----date/time------ path``1      2936832 Mar 01 2002 01:02:58 c3725-adventerprisek9-mz.124-15.T14.bin``64000000 bytes available (2936832 bytes used)` | | **14** | 如果您正在上传一个 IOS 文件,如本例所示,通过 TFTP 完成文件传输需要一段时间。TFTP 协议使用 UDP 端口进行文件传输,但在实验室和生产环境中都是一种慢速文件传输协议。您只需上传一个文件来学习使用 Python 3 脚本进行 TFTP 文件传输的概念。您无法更新启动系统配置,也无法使用新的 IOS 映像重新启动 GNS3 IOS 路由器;这是在 GNS3 上使用 IOS 的缺点之一。但是,在 VMware Workstation 上使用 Cisco CSR1000v 或 Nexus 9000v,可以模拟实际的 IOS XE 升级过程,包括引导到新升级的 IOS。在下一章中,我们将在 VMware Workstation 上使用 Cisco CSR1000v 并模拟完整的 IOS 升级过程。一旦文件大小达到预期的文件大小,您就知道脚本和文件传输已经成功完成。`R1#` `dir``Directory of flash:/``1  -rw-    46380064   Mar 1 2002 01:22:04 +00:00  c3725-adventerprisek9-mz.124-15.T14.bin``66936832 bytes total (20553728 bytes free)``R1#``show flash``-#- --length-- -----date/time------ path``1     46380064 Mar 01 2002 01:22:04 c3725-adventerprisek9-mz.124-15.T14.bin``20553728 bytes available (46383104 bytes used)` | | **15** | 由于缓慢的上传时间和 Python 的`telnetlib`库的问题,你的 Ubuntu Python 服务器控制台可能仍然看起来像是在上传 IOS。按 Ctrl+Z 退出此状态以完成本实验。`pynetauto@ubuntu20s1:~/ch11$` `python3 upload_ios.py``Enter your telnet username: pynetauto``Password:``^Z``[1]+  Stopped                 python3 upload_ios.py``pynetauto@ubuntu20s1:~/ch11$` | * 检查 CentOS8.1 上的 TFTP 服务是否正常运行 * 将 R1 的运行配置备份到 TFTP 服务器。 * 上传一个 IOS 到 R1 的闪存进行快速测试。 你已经使用远程登录和 TFTP 服务上传文件到 R1 的闪存。网络工程师的大部分工作涉及 IOS 补丁管理,包括将 IOS 上传到许多路由器和交换机。想象一下,你正在编写一段代码,这段代码可以在晚上你安然入睡时将多个 IOS 文件上传到数百台思科路由器和交换机。实际上,这是我为定期修补客户网络设备而创建的首批工具之一。 ## 复制(克隆)GNS3 项目 为了准备下一章,让我们复制和克隆当前项目。大约有三种不同的方法可以制作 GNS3 项目的副本。第一种方法是使用 GNS3 菜单的“将项目另存为…”功能,第二种方法是将其导出为可移植项目并在另一台主机上重新导入,最后一种方法是使用“导出配置”方法导出您设备的配置并手动克隆您的 GNS3 项目。这些方法是按照难度增加的顺序提到的。另外,请注意,如果您使用第一种方法,您现有的实验室配置将被移动到新保存的`Project`文件夹中。您将丢失设备运行配置的现有配置。克隆 GNS3 项目的最佳方式是使用第三种方法,它保留您现有的实验室状态,并将相同的设置带到新项目中。最后,但也是最重要的,如果你想在任何时候对你的 GNS3 项目做一个完整的备份,你可以在`C:\Users\[your_name]\GNS3\project`下复制整个项目文件夹。 因为在本书的这一点之外我们不会使用`linuxvm2ios`项目,所以让我们看看如何使用复制方法克隆 GNS3 项目。在后面的章节中,将演示另一种方法,为您提供另一个克隆选项。 | **#** | **任务** | | --- | --- | | **1** | Make sure all devices in the GNS3 project are powered off. To save the existing `linuxvm2ios` project to a new project name, first open GNS3’s main user window, go to File, and select “Save project as.” See Figure 11-13.![img/492721_1_En_11_Fig13_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig13_HTML.jpg)图 11-13。GNS3,“项目为…”菜单 | | **2** | GNS3 项目保存在`C:\Users\[your_name]\GNS3\project`文件夹下。现在,给你的第十二章的新项目起一个新名字。本实验将在后续实验中使用。参见图 11-14 。GNS3 project folder location: `C:\Users\brendan\GNS3\projects`![img/492721_1_En_11_Fig14_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig14_HTML.jpg)图 11-14。GNS3,将现有的 linuxvm2ios 项目克隆到 cmllab-basic | | **3** | After you saved the new project, go to the `Project` folder (`C:\Users\[your_name]\GNS3\projects`) and check the original and new project folders’ file sizes. The original project folder size will be around 24KB in size, whereas the new project folder size will be over 2.1MB. Open the folder and check the folder and file contents. See Figure 11-15.![img/492721_1_En_11_Fig15_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig15_HTML.jpg)图 11-15。主机,确认新的项目文件夹 | | **4** | Now open the new project and run it. If you have saved the project correctly, it should run smoothly with no errors. Now you are ready for some CML-PE labs in Chapter 12. See Figure 11-16.![img/492721_1_En_11_Fig16_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_11_Fig16_HTML.jpg)图 11-16。GNS3,运行 cmllab-basic 项目 | ## 摘要 在本章中,您通过 Telnet 测试了 Linux 虚拟机与 IOS 路由器 R1 的交互。您学习了如何创建 GNS3 项目,使用简单的 Cisco 配置运行了一个快速文件传输测试实验室,最后学习了如何克隆 GNS3 项目。你现在已经做好准备,可以处理第 12 到 18 章中的其他实验。在第十二章中,我们将使用从前面章节中获得的许多技能,使用集成到 GNS3 中的 Cisco CML (VIRL)镜像来构建 L3 路由和 L2 交换实验室。我们将在第十三章中使用`telnetlib`模块,向 Python 网络自动化迈出一小步,然后在第十四章中学习使用 SSH 协议控制思科 VIRL 设备。Python 脚本将在 Linux 虚拟机上运行,使用`telnetlib`、`paramiko`和`netmiko`库通过 Telnet 和 SSH 协议控制思科 VIRL 设备。 # 十二、构建 Python 自动化实验室环境 到目前为止,我们一直在 Telnet 实验室中测试运行过时 IOS 版本(12.x)的旧 Cisco 设备。为了在较新的 IOS 版本上测试新功能,并为我们的实验室配备交换(L2)功能,我们需要将 Cisco CML-PE 的 L2 和 L3 映像集成到 GNS3 实验室环境中。本章将指导您在 GNS3 上完成思科 CML-PE L2 和 L3 映像集成。将指导您完成在 GNS3 环境中下载和集成 L2 和 L3 映像的每个步骤,以便您可以在下一章的网络实验室中使用更新的 IOS 功能集进行测试。您将了解 CML-PE 产品,下载实验室所需的图像,在 GNS3 上集成下载的图像,然后构建 CML-PE 基本实验室拓扑,以测试以下章节中的各种 Python 网络实验室。 ![img/492721_1_En_12_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Figa_HTML.jpg) 在本书中,你已经在你的第一次网络自动化之旅中走了很长的路。到目前为止,我希望您没有失去学习 Python 网络自动化的热情。为了从本书的后半部分获得最大收益,本书假设你已经彻底阅读了前 11 章。在 Python 网络自动化旅程的前半部分,您已经掌握了使用大量开源和专有软件安装、运行和配置集成虚拟实验室所需的各种基本实践技能。您还开始使用目前所学的知识编写基本的 Python 脚本。当然,我们还可以讨论更多令人兴奋的话题,比如 DevOps 概念、Ansible、ACI、REST API、HTML、Postman、YANG、NETCONF 等等。然而,这本书的目的是明确的:我们一直关注让你开始 Python 编程的本质任务,这是所有 Python 应用开发工程师的基本技能。你可能听过这样一句话“给一个人一条鱼,你喂他一天;教一个人钓鱼,你喂他一辈子。”借用 Python 网络应用开发的这个伟大成语,“教一个网络工程师如何使用 Ansible,你让他成为一个驱动;教他怎么用 Python 写代码,你就把他变成一辈子机械师。”因此,扩展你从阅读本书中获得的技能是你的责任;只有你能决定你下一步要去哪里,以及如何将新技能应用到你的工作或学习中。 到目前为止,您已经学习了各种 IT 技能,为接下来主要关注 Python 网络自动化实验室的章节做好了准备。如果您到目前为止没有遇到任何技术问题,那么您应该能够轻松地阅读剩余的章节。如果您还没有完全设置好您的实验环境,或者如果您只完成了每章中的部分练习,我建议您回去完成,因为您将需要所有这些基础知识。 在本章中,我们将从第十一章结束时停止的地方开始,完成构建的基本 CML lab-基本实验室拓扑,以开始编写 Python 代码并测试用于网络自动化应用开发的代码。假设您是一名经验丰富的网络工程师,拥有多年使用 Cisco、Juniper、Arista 和 HP 网络设备的经验。在这种情况下,您在学习本实验的内容时不会有任何问题。然而,即使您是一名刚刚开始职业生涯的网络工程师,您仍然能够按照所有实验步骤进行操作,因为这些实验只包含最基本的网络概念,并附有足够的培训说明。如果你集中注意力,你将能够毫不费力地完成所有的实验。 ## 思科 CML-个人软件许可信息和软件下载 思科出售思科建模实验室-个人(CML-个人)与年度订阅。如果您是一名网络工程师或网络专业的学生,想要探索 Cisco CML-PERSONAL 所提供的一切,购买订阅是一个不错的选择。在订阅的第一年,您需要支付 199 美元,并且您将继续支付思科学习团队指定的相同的年度订阅费。对于一个以思科技术为生的专业工程师来说,与传统网络工程师为了进入这个行业而不得不花费在他们的旧的 3700 和 2600 系列路由器和 2950 系列交换机上的费用相比,订阅费是便宜的。无论如何,如果你能得到 CML 图像,有更具成本效益的方法来建立你的实验室,比如将它们集成到 GNS3 中,这是本章的主题。 正如在第一章中提到的,CML-PERSONAL(改名为 VIRL-PE,虚拟互联网路由实验室个人版)是一个虚拟设备仿真器程序,用于研究思科的技术,所以你可以说它是特定于供应商的。然而,您将从思科工具中学到的网络概念将是通用的,并可复制到其他供应商的技术中。一旦您订阅了 Cisco CML-PERSONAL,您就可以下载并安装 Cisco 的 CML-PERSONAL 软件,并使用路由器、交换机和防火墙 CML-PERSONAL 映像来模拟各种 Cisco 实验室。不出所料,本书没有使用 CML-PERSONAL PE,而是使用混合实验室设置,在 GNS3 上使用 Cisco CML-PERSONAL 的路由器(L3)和交换机(L2)映像。GNS3 上的 CML-个人映像安装和集成程序与 GNS3 上的 Cisco 路由器 IOS 映像集成几乎相同。同样,您的 Windows 主机需要连接到互联网。CML-PERSONAL 是思科的专有软件,法律禁止免费分发 CML-PERSONAL 软件和 CML-PERSONAL 图像。如果您有能力订阅,我们推荐您在 [`https://learningnetworkstore.cisco.com/cisco-modeling-labs-personal/cisco-cml-personal`](https://learningnetworkstore.cisco.com/cisco-modeling-labs-personal/cisco-cml-personal) 订阅 CML-PERSONAL。 本书声明任何 Cisco IOS、CML-PERSONAL 或任何其他专有软件的购买和使用都是您的责任。本书不包含任何软件。有几种方法可以找到本书中提到的软件。更多信息请参考 [`https://developer.cisco.com/modeling-labs/`](https://developer.cisco.com/modeling-labs/) 。 ## 下载 Cisco CML-个人 IOSvL2(交换机)镜像 在我们将 Cisco CML-PERSONAL L2 交换机映像集成到 GNS3 之前,您需要找到、下载并保存到您的主机 PC 的`Downloads`文件夹中。您可以从 GNS3 官方网站获得有关 CML-PERSONAL L2 交换机的信息,该网站将带您到思科的 CML-PERSONAL L2 网站( [`https://learningnetwork.cisco.com/s/article/iosvl2-more-info-updated-10-2-15-x`](https://learningnetwork.cisco.com/s/article/iosvl2-more-info-updated-10-2-15-x) )。 本书使用的 CML-PERSONAL switch 镜像名称为`vios_l2-adventerprisek9-m.03.2017.qcow2`。您可以使用同一个或您可以找到并下载的任何更新的图像。下载交换机镜像文件后,保存到`Downloads`文件夹。将其保存到`Downloads`文件夹对于节省时间很重要,因为 GNS3 将首先在`Downloads`文件夹中查找图像(参见图 12-1 )。 ![img/492721_1_En_12_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig1_HTML.jpg) 图 12-1。 CML-个人 L2 交换机图像,保存到下载文件夹 ## 下载 Cisco CML-PERSONAL IOSv(路由器)镜像和启动配置文件 要集成 CML-PERSONAL L3 或路由器映像,您必须下载工作 IOSv L3 Qemu ( `qcow2`)和`IOSv_startup_config.img`映像文件。与 L2 交换机不同,CML-PERSONAL 路由器镜像与 GNS3 的集成也需要用于设备启动的`IOSv_startup_config.img`文件,该文件可以从 CML-PERSONAL 的官方网站下载,或者关于 GNS3 CML-PERSONAL L3 路由器和`IOSv_startup_config.img`的`sourceforge.net.`信息可以从思科的 CML-PERSONAL 网站获得。请访问以下推荐网站,了解有关 Cisco CML-PERSONAL L3 软件的更多信息: * *GNS 3 CML-个人 IOSv* : [`https://learningnetwork.cisco.com/s/article/iosv-more-info-updated-4-20-15-x`](https://learningnetwork.cisco.com/s/article/iosv-more-info-updated-4-20-15-x) * *CML-个人 IOSv_startup_config 文件下载* : [`https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/`](https://sourceforge.net/projects/gns-3/files/Qemu%2520Appliances/) 本书使用的 CML-PERSONAL IOSv 或 L3 路由器镜像名为`IOSv-L3-15.6(2)T.qcow2`;这是一个旧的图像,但它仍然符合我们的需要。由于我们没有探索所有的网络功能,思科设备仅满足我们学习网络自动化概念和 Python 应用开发的需要。我们可以使用任何版本的 CML L2 和 L3 映像,只要它能够承载流量,并节省我们运行成熟的基于硬件的实验室所需的时间和资源。尽管我们关注的是网络自动化,但路由和交换概念并不是本书的核心。因此,Cisco CML-PERSONAL images 为我们提供了基本的 L2 和 L3 连接来承载流量,并使用 Telnet、SSH、RESTCONF 和其他 Python 库来学习 Python 脚本编写技能。下载两个需要的文件,如图 12-2 所示,保存在`Downloads`文件夹中。 ![img/492721_1_En_12_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig2_HTML.jpg) 图 12-2。 CML-个人 L3 路由器映像,保存在下载文件夹中 一旦有了 vIOS L2 映像、vIOS L3 映像和 vIOS 启动配置文件,就可以将它们安装并集成到 GNS3 中了。 ## 在 GNS3 上安装思科 CML-个人 L2 交换机和 CML-个人 L3 在第十一章的结尾,我们复制并创建了一个名为`cmllab-basic`的新项目。让我们重新打开那个项目,因为这是我们本章任务的起点。如果您从第十一章继续工作,请从 GNS3 继续工作,但是如果您刚刚打开 PC,那么首先使用桌面上的 GNS3 快捷图标启动 GNG3。如果实验环境设置正确,GNS3 将运行,安装在 VMware Workstation 上的 GNS3 虚拟机将在几秒钟后自动启动。 ![img/492721_1_En_12_Figb_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Figb_HTML.jpg)Installation tip 如果由于软件故障导致 CML-PERSONAL 安装无法正常工作,您必须通过右键单击 GNS3 删除已安装的交换机或路由器模板,并尝试再次安装映像。CML-个人图像不是为 GNS3 制作的,所以有时它们在第一次尝试时不会集成到 GNS3。如果 CML-PERSONAL 映像在第一次尝试后没有正确安装,不要放弃;继续尝试,直到它被正确安装。 ## 在 GNS3 上安装思科 CML-个人 L2 交换机 您将首先在 GNS3 上安装思科 CML-个人 L2 交换机镜像。为了正确安装,主机必须连接到互联网。安装完成后,您将使用安装的映像运行交换实验,这在早期的 IOS 映像集成中是不可能的。 | # | 工作 | | --- | --- | | **1** | In the GNS3 main window, go to File and click + New template . See Figure 12-3.![img/492721_1_En_12_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig3_HTML.jpg)图 12-3。CML L2 交换机安装,添加新模板 | | **2** | When the “New template” wizard opens , leave the default selection of “Install an appliance from the GNS3 server (recommended)” and click Next. See Figure 12-4.![img/492721_1_En_12_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig4_HTML.jpg)图 12-4。CML L2 交换机安装,新模板选择 | | **3** | In the “Appliances from server**”** window , in the search field, type in **IOSvL2** to locate Cisco IOSvL2 Qemu under Switches. Select the searched device and click Install; this is the template for the Cisco CML-PERSONAL L2 switch on GNS3\. See Figure 12-5.![img/492721_1_En_12_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig5_HTML.jpg)图 12-5。CML L2 交换机安装,来自服务器的设备 | | **4** | In the Server window , click Next. See Figure 12-6.![img/492721_1_En_12_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig6_HTML.jpg)图 12-6。CML L2 交换机安装,服务器 | | **5** | In the “Qemu settings” window , click Next. See Figure 12-7.![img/492721_1_En_12_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig7_HTML.jpg)图 12-7。CML L2 交换机安装,Qemu 设置 | | **6** | Under “Required files,” if you have placed the CML-PERSONAL L2 `.qcow2` file in the `Downloads` folder, the installation wizard will automatically detect the L2 image automatically and display it in green. You can also see the missing files in red. Highlight your image and click Next. See Figure 12-8.![img/492721_1_En_12_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig8_HTML.jpg)图 12-8。CML L2 交换机安装,所需文件 | | **7** | When the Appliance window pops up , click Yes. See Figure 12-9.![img/492721_1_En_12_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig9_HTML.jpg)图 12-9。CML L2 安装,设备安装弹出窗口 | | **8** | To complete the installation , click Finish. See Figure 12-10.![img/492721_1_En_12_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig10_HTML.jpg)图 12-10。CM L2 开关的安装、使用 | | **9** | The “Add template” window pops up ; click OK to finish the installation. See Figure 12-11.![img/492721_1_En_12_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig11_HTML.jpg)图 12-11。CML L2 交换机安装,弹出“添加模板” | | **10** | On GNS3, click the Switch button on the left, which looks like two opposite arrows, to open the switches. See Figure 12-12.![img/492721_1_En_12_Fig12_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig12_HTML.jpg)图 12-12。CML L2 交换机安装,选择 GNS3 上的交换机图标 | | **11** | If Cisco CML-PERSONAL IOSvL2 was installed correctly, you would see your new switch devices installed under Switches. See Figure 12-13.![img/492721_1_En_12_Fig13_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig13_HTML.jpg)图 12-13。CML L2 交换机安装,选择 Cisco IOSvL2 交换机 | | **12** | Now, drag and drop the IOSvL2 switch to the cmllab-basic lab, as shown in Figure 12-14.![img/492721_1_En_12_Fig14_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig14_HTML.jpg)图 12-14。CML L2 交换机安装,拖放 CML 交换机 | | **13** | Using the Add Link tool, connect Gi0/0 of the new switch to Switch1’s e2 interface, as shown in Figure 12-15. Then power on all devices on the topology and also the CentOS8.1 and Ubuntu 20.04 LTS servers on VMware Workstation 15 Pro. Rename the switch to LAB-SW1 by right-clicking and clicking the Change Hostname menu.![img/492721_1_En_12_Fig15_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig15_HTML.jpg)图 12-15。CML L2 交换机安装,将 CML 交换机连接到网络 | | **14** | After powering on LAB-SW1, open its console, and you will see a similar power-on screen as shown in Figure 12-16.![img/492721_1_En_12_Fig16_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig16_HTML.jpg)图 12-16。CML L2 交换机安装,通电 CML 交换机屏幕 | | **15** | 如果新交换机成功启动,它的控制台屏幕应该如下所示。现在,您已经在 GNS3 上完成了 CML 个人 L2 交换机的安装。为一些交换实验室做好准备。 | |   | `Cisco IOS Software, vios_l2 Software (vios_l2-ADVENTERPRISEK9-M), Experimental Version 15.2(20170321:233949) [mmen 101]` | |   | `Copyright (c) 1986-2017 by Cisco Systems, Inc.` | |   | `Compiled Wed 22-Mar-17 08:38 by mmen` | |   | `**************************************************************************` | |   | `* IOSv is strictly limited to use for evaluation, demonstration and IOS  *` | |   | `* education. IOSv is provided as-is and is not supported by Cisco's      *` | |   | `* Technical Advisory Center. Any use or disclosure, in whole or in part, *` | |   | `* of the IOSv Software or Documentation to any third party for any       *` | |   | `*` | |   | `*Jan  9 10:36:48.512: %PLATFORM-5-SIGNATURE_VERIFIED: Image 'flash0:/vios_l2-adventerprisek9-m' passed code signing verification purposes is expressly prohibited except as otherwise authorized by     *` | |   | `* Cisco in writing.                                                      *` | |   | `**************************************************************************` | |   | `Switch>` | ### GNS3 上 CML L2 交换机集成的快速通信测试 在 CML-PERSONAL L2 交换机安装结束时,快速执行从交换机到网络上其它设备的通信检验。推荐的 ICMP 测试描述如下: | # | 工作 | | --- | --- | | **1** | 快速登录并使用有效的 IP 地址在新交换机上配置正确的交换机名称和 VLAN 1 接口。 | |   | `Switch>``enable` | |   | `Switch#``configure terminal` | |   | `LAB-SW1(config)#``hostname LAB-SW1` | |   | `LAB-SW1(config)#``spanning-tree vlan 1 root primary` | |   | `LAB-SW1(config)#``interface vlan 1` | |   | `LAB-SW1(config-if)#``ip add 192.168.183.101 255.255.255.0` | |   | `LAB-SW1(config-if)#``no shut` | |   | `LAB-SW1(config-if)#``end` | | **2** | 在 LAB-SW1 上,当您打开 Gi0/0 时,R1 的 f0/0 接口可能会提示您双工不匹配错误。要在 LAB-SW1 上停止这条恼人的 CDP 错误消息,您需要将 R1 的 f0/0 接口速度硬编码为 100,并将双工编码为全双工。如果没有出现此错误,您可以继续下一步。 | |   | `LAB-SW1(config-if)#` | |   | `*Aug 2 11:16:38.977: %CDP-4-DUPLEX_MISMATCH: duplex mismatch discovered on GigabitEthernet0/0 (not half duplex), with R1 FastEthernet0/0 (half duplex).` | |   | 打开 R1 控制台,按如下方式更改接口速度和双工: | |   | `R1#``configure terminal` | |   | `R1(config)#``interface f0/0` | |   | `R1(config-if)#``duplex full` | |   | `R1(config-if)#``speed 100` | |   | `R1(config-if)#``do write memory` | | **3** | 转到 PC1,运行`ip dhcp`命令获取 IP 地址,或者如果您继续上一章的实验,运行`show ip`命令检查 PC1 的 IP 地址。 | |   | `PC1>``ip dhcp` | |   | `DDORA IP 192.168.183.129/24 GW 192.168.183.2` | |   | `PC1>``show ip` | |   | `NAME        : PC1[1]` | |   | `IP/MASK     : 192.168.183.129/24` | |   | `GATEWAY     : 192.168.183.2` | |   | `DNS         : 192.168.183.2` | |   | `DHCP SERVER : 192.168.183.254` | |   | `DHCP LEASE  : 1713, 1800/900/1575` | |   | `DOMAIN NAME : localdomain` | |   | `MAC         : 00:50:79:66:68:00` | |   | `LPORT       : 20012` | |   | `RHOST:PORT  : 127.0.0.1:20013` | |   | `MTU:        : 1500` | | **4** | 从实验室-SW1 (192.168.183.101)向 PC1 (192.168.183.129)和 R1 的 f0/0 (192.168.183.133)发送 ICMP 消息。 | |   | `LAB-SW1#` `ping 192.168.183.129` | |   | `Type escape sequence to abort.` | |   | `Sending 5, 100-byte ICMP Echos to 192.168.183.129, timeout is 2 seconds:` | |   | `.!!!!` | |   | `Success rate is 100 percent (5/5), round-trip min/avg/max = 2/3/5 ms` | |   | `LAB-SW1#` `ping 192.168.183.133` | |   | `Type escape sequence to abort` `.` | |   | `Sending 5, 100-byte ICMP Echos to 192.168.183.133, timeout is 2 seconds:` | |   | `.!!!!` | |   | `Success rate is 80 percent (4/5), round-trip min/avg/max = 2/4/6 ms` | | **5** | 确保两台 Linux 服务器都已启动并在 VMware Workstation 上运行。从上一章我们知道`centos8s1`服务器的 IP 地址是 192.168.183.130,`ubuntu20s1`服务器的 IP 地址是 192.168.183.132。 | |   | 从 LAB-SW1 向`centos8s1`和`ubuntu20s1`发送 ICMP 数据包(ping)。您的服务器 IP 地址可能与此处显示的示例不同: | |   | `LAB-SW1#` `ping 192.168.183.130` | |   | `Type escape sequence to abort.` | |   | `Sending 5, 100-byte ICMP Echos to 192.168.183.130, timeout is 2 seconds:` | |   | `!!!!!` | |   | `Success rate is 100 percent (5/5), round-trip min/avg/max = 3/6/13 ms` | |   | `LAB-SW1#` `ping 192.168.183.132` | |   | `Type escape sequence to abort` `.` | |   | `Sending 5, 100-byte ICMP Echos to 192.168.183.132, timeout is 2 seconds:` | |   | `!!!!!` | |   | `Success rate is 100 percent (5/5), round-trip min/avg/max = 2/3/6 ms` | | **6** | 通信测试成功后,将运行配置复制到启动配置。 | |   | `LAB-SW1#``copy running-config startup-config` | |   | `Destination filename [startup-config]?` | |   | `Building configuration...` | |   | `Compressed configuration from 3559 bytes to 1604 bytes[OK]` | 现在,您已经在 GNS2 上完成了 CML L2 交换机映像的集成和验证。有了这个图像,我们可以用许多交换机来测试多设备 Python 网络自动化实验室。接下来,继续集成 L3 路由器映像。 ## 在 GNS3 上安装 Cisco CML-PERSONAL L3 路由器 现在,您将按照本节所示的步骤在 GNS3 上安装 CML-PERSONAL L3 路由器;这将使您能够在 GNS3 上运行 CML 个人路由器。和往常一样,您需要连接到互联网才能完成此过程。最好是连接到家庭网络,而不是在安装过程中工作。请注意,如果您的主机通过公司代理连接到互联网,您可能会遇到一些问题。参见图 12-17 。 ![img/492721_1_En_12_Fig17_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig17_HTML.jpg) 图 12-17。 GNS3,CML-个人 L3 图像错误 ![img/492721_1_En_12_Figc_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Figc_HTML.jpg) CML 图像的集成看起来很正常,直到您将图标拖放到 GNS3 拓扑画布上。如果 GNS3 产生一个`qcow2`或其他错误并且不工作,您可能需要使用如图 12-18 所示的“删除模板”选项,并从头开始整合过程。 ![img/492721_1_En_12_Fig18_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig18_HTML.jpg) 图 12-18。 GNS3,删除 CML-个人 L3 模板 现在,为 CML L3 路由器映像添加一个新模板,并完成集成。 | # | 工作 | | --- | --- | | **1** | In the GNS3 main window , go to File and click “+ New template.” See Figure 12-19.![img/492721_1_En_12_Fig19_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig19_HTML.jpg)图 12-19。CML L3 路由器安装,添加新模板 | | **2** | When the “New template ” wizard opens, leave the default selection of “Install an appliance from the GNS3 server (recommended)” and click Next. See Figure 12-20.![img/492721_1_En_12_Fig20_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig20_HTML.jpg)图 12-20。CML L3 路由器安装,新模板 | | **3** | In the “Appliances from server ” window, type **IOSv** in the search field to locate Cisco IOSv Qemu under Switches. Select the searched device and click Install. This is the template for the Cisco CML-PERSONAL L3 router on GNS3\. See Figure 12-21.![img/492721_1_En_12_Fig21_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig21_HTML.jpg)图 12-21。CML L3 路由器安装,来自服务器的设备 | | **4** | In the next window , click Next. See Figure 12-22.![img/492721_1_En_12_Fig22_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig22_HTML.jpg)图 12-22。CML L3 路由器安装,服务器选择 | | **5** | On the “Qemu settings” screen , click Next again. See Figure 12-23.![img/492721_1_En_12_Fig23_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig23_HTML.jpg)图 12-23。CML L3 路由器安装,Qemu 设置 | | **6** | It is recommended that you click the actual vIOS name under “Required files” and highlight it before clicking Next. Notice that the actual image name `vios-adventerprisek9-m.vmdk.SPA.156-2.T` is selected before clicking the Next button . See Figure 12-24.![img/492721_1_En_12_Fig24_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig24_HTML.jpg)图 12-24。CML L3 路由器安装,所需文件 | | **7** | When the Appliance window pops up , click Yes. See Figure 12-25.![img/492721_1_En_12_Fig25_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig25_HTML.jpg)图 12-25。CML L3 路由器安装,设备安装弹出窗口 | | **8** | In the Usage window , click Finish. See Figure 12-26.![img/492721_1_En_12_Fig26_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig26_HTML.jpg)图 12-26。CML L3 路由器的安装、使用 | | **9** | When the “Add template ” message appears, click OK to complete the CML-PERSONAL L3 router image installation. See Figure 12-27.![img/492721_1_En_12_Fig27_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig27_HTML.jpg)图 12-27。CML L3 路由器安装,弹出“添加模板” | | **10** | Go back to the main user window , and this time click the Router icon that looks like a circle with four arrows. See Figure 12-28.![img/492721_1_En_12_Fig28_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig28_HTML.jpg)图 12-28。CML L3 路由器安装,单击 GNS3 上的路由器图标 | | **11** | The Routers list appears as shown in Figure 12-29, and you will immediately notice that a new IOSv router has been added.![img/492721_1_En_12_Fig29_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig29_HTML.jpg)图 12-29。CML L3 路由器安装,选择 CML 路由器 | | **12** | Now drag and drop the new router onto the Topology canvas of the `cmllab-basic` project. Rename the router as LAB-R1\. See Figure 12-30.![img/492721_1_En_12_Fig30_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig30_HTML.jpg)图 12-30。CML L3 路由器安装,拖放 CML 路由器 | | **13** | 重命名新路由器后,使用添加链路(连接器)工具,将 R1 实验室的 Gi0/0 接口连接到实验室 SW1 的 Gi0/1 接口。您可以在另一台设备打开时建立连接。现在打开 R1 实验室的电源,打开控制台窗口。见图 12-31 。 | |   | The Topology Summary window shows the virtual connection between devices (see Figure 12-32).The Servers Summary window shows where each device is hosted (see Figure 12-33).![img/492721_1_En_12_Fig31_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig31_HTML.jpg)图 12-31。CML L3 路由器安装、连接和启动 CML 路由器![img/492721_1_En_12_Fig32_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig32_HTML.jpg)图 12-32。拓扑摘要窗口![img/492721_1_En_12_Fig33_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig33_HTML.jpg)图 12-33。服务器摘要窗口 | | **14** | When the router boots up, it will display the vIOS version during the bootup. See Figure 12-34.![img/492721_1_En_12_Fig34_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig34_HTML.jpg)图 12-34。CML L3 路由器安装,开机 CML 路由器屏幕 | | **15** | 如果您看到类似图 12-35 的控制台屏幕,这意味着您已经在 GNS3 上完成了 CML-PERSONAL L3 路由器。CML-PERSONAL 与 Cisco IOS 15.x 几乎完全相同。现在,您可以使用较新的 IOS 映像进行一些路由实验了。 | |   | `Cisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)` | |   | `Technical Support: http://www.cisco.com/techsupport` | |   | `Copyright (c) 1986-2016 by Cisco Systems, Inc.` | |   | `Compiled Tue 22-Mar-16 16:19 by prod_rel_team` | |   | `*Jan  9 11:12:35.500: %CRYPTO-6-ISAKMP_ON_OFF: ISAKMP is OFF` | |   | `*Jan  9 11:12:35.501: %CRYPTO-6-GDOI_ON_OFF: GDOI is OFF` | |   | `*Jan  9 11:12:37.708: %SYS-3-CPUHOG: Task is running for (1999)msecs, more than (2000)msecs (0/0),process = Crypto CA.` | |   | `**************************************************************************` | |   | `* IOSv is strictly limited to use for evaluation, demonstration and IOS  *` | |   | `* education. IOSv is provided as-is and is not supported by Cisco's      *` | |   | `* Technical Advisory Center. Any use or disclosure, in whole or in part, *` | |   | `* of the IOSv Software or Documentation to any third party for any       *` | |   | `* purposes is expressly prohibited except as otherwise authorized by     *` | |   | `* Cisco in writing` `.                                                      *` | |   | `**************************************************************************` | |   | `Router#` | ### GNS3 上集成 CML L3 路由器的快速通信测试 完成以下任务,让新安装的路由器能够与拓扑中的其余设备通信: | # | 工作 | | --- | --- | | **1** | 快速登录新路由器并配置正确的路由器名称。此外,从与 VMnet8 (192.168.183.0/24)相同的子网配置一个有效的 IP 地址,以便与 gibbit Ethernet 0/0 接口。请注意,您的子网可能不同。确保发出`no shut`命令调出界面。 | |   | `Router#``configure termina` | |   | `Router(config)#``hostname` | |   | `LAB-R1(config)#``interface GigabitEthernet 0/0` | |   | `LAB-R1(config-if)#``ip add 192.168.183.10 255.255.255.0` | |   | `LAB-R1(config-if)#``no shut` | |   | `LAB-R1(config-if)#``end` | | **2** | 在 R1 实验室路由器上,一旦 Gi0/0 接口出现,就开始向以下 IP 地址发送 ICMP 数据包,以确保 R1 实验室能够与拓扑中的所有其它设备通信。VMnet8 的默认网关 IP (192.168.183.2)、PC1 的 IP 地址(192.168.183.129)、R1 的 f0/0 (192.168.183.133)、`centos8s1`服务器(192.168.183.130)和`ubuntu20s1`服务器(192.168.183.132)。 | |   | `LAB-R1#``ping 192.168.183.2` | |   | `Type escape sequence to abort.` | |   | `Sending 5, 100-byte ICMP Echos to 192.168.183.2, timeout is 2 seconds:` | |   | `.!!!!` | |   | `Success rate is 80 percent (4/5), round-trip min/avg/max = 8/10/11 ms` | |   | `LAB-R1#``ping 192.168.183.129` | |   | `Type escape sequence to abort.` | |   | `Sending 5, 100-byte ICMP Echos to 192.168.183.129, timeout is 2 seconds:` | |   | `.!!!!` | |   | `Success rate is 80 percent (4/5), round-trip min/avg/max = 7/9/12 ms` | |   | `LAB-R1#``ping 192.168.183.133` | |   | `Type escape sequence to abort.` | |   | `Sending 5, 100-byte ICMP Echos to 192.168.183.133, timeout is 2 seconds:` | |   | `.!!!!` | |   | `Success rate is 80 percent (4/5), round-trip min/avg/max = 12/15/18 ms` | |   | `LAB-R1#``ping 192.168.183.130` | |   | `Type escape sequence to abort.` | |   | `Sending 5, 100-byte ICMP Echos to 192.168.183.130, timeout is 2 seconds:` | |   | `.!!!!` | |   | `Success rate is 80 percent (4/5), round-trip min/avg/max = 8/8/11 ms` | |   | `LAB-R1#``ping 192.168.183.132` | |   | `Type escape sequence to abort.` | |   | `Sending 5, 100-byte ICMP Echos to 192.168.183.132, timeout is 2 seconds:` | |   | `.!!!!` | |   | `Success rate is 80 percent (4/5), round-trip min/avg/max = 8/9/10 ms` | | **3** | 仅出于验证目的,我们还可以使用`ssh –l username server_IP`命令快速测试从 R1 实验室登录 CentOS8.1 服务器的 SSH 登录。 | |   | `LAB-R1#``ssh -l pynetauto 192.168.183.130` | |   | `Password:` | |   | `Activate the web console with: systemctl enable --now cockpit.socket` | |   | `Last login: Sat Jan  9 00:00:39 2021 from 192.168.183.1` | |   | `[pynetauto@centos8s1 ~]$` `exit` | |   | `logout` | |   | `[Connection to 192.168.183.130 closed by foreign host]` | |   | `LAB-R1#` | |   | 请注意,由于安全算法不匹配,SSH 无法登录到`ubuntu20s1`服务器。SSH 登录会话将从服务器端启动到网络设备,在这个阶段您不必担心这个问题。 | | **4** | 完成通信测试后,将 R1 实验室的运行配置保存到启动配置中。 | |   | `LAB-R1#` `copy running-config startup-config` | |   | `Destination filename [startup-config]?` | |   | `Building configuration...` | |   | `[OK]` | ## 构建 CML 个人实验室拓扑 在进入下一章的实际实验任务之前,让我们向`cmllab-basic` GNS3 项目添加一些设备,并完成我们的拓扑。此步骤假设您从上一步开始继续构建拓扑。 | # | 工作 | | --- | --- | | **1** | Continuing from the previous section, add one more IOSvL2 switch and one more IOSv router to the topology as shown in Figure 12-35. Rename the switch to `lab-sw2` in lowercase and the router to `lab-r2` also in lowercase. The device names are not capitalized and standardized on purpose here, although it is a best practice to follow a strict naming convention for your network devices. In production, this convention is not followed by every engineer and becomes an annoyance. Hence, to emulate the imperfect production environment, we will not follow a strict device naming convention. Connect Gi0/0 of `lab-sw2` to Gi0/2 of LAB-SW1 and Gi0/1 of `lab-sw2` to Gi0/1 of lab-r2\. Also, connect Gi0/0 of lab-r2 to Gi0/1 of LAB-R1.![img/492721_1_En_12_Fig35_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig35_HTML.jpg)图 12-35。配置 CML 实验室拓扑,三种方法之一 | | **2** | Now power on lab-sw2 and lab-r2 as shown in Figure 12-36.![img/492721_1_En_12_Fig36_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig36_HTML.jpg)图 12-36。配置 CML 实验室拓扑,三个中的两个 | | **3** | 在这一阶段,包括`centos8s1` IP 服务服务器和`ubuntu20s1` Python 服务器在内的所有设备都应该通电并平稳运行。在 GNS3 网络和 Linux 服务器之间执行 ping 测试,以确保所有设备都可以连接到 VMnet8 网络上的其他设备。 | | **4** | 以下是 R1 IOS 路由器的配置: | |   | 首先,在 R1 (IOS 路由器)上,让我们打开控制台手动分配 IP 地址,这样它的 IP 地址就不会在每次 DHCP 租用时间过去时发生变化。 | |   | `R1#``show ip interface brief` | |   | `Interface                  IP-Address      OK? Method Status                Protocol` | |   | `FastEthernet0/0            192.168.183.133 YES DHCP   up                    up` | |   | `FastEthernet0/1            7.7.7.2         YES NVRAM  up                    up` | |   | `FastEthernet1/0            unassigned      YES NVRAM  administratively down down` | |   | `R1#configure terminal` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `R1(config)#``interface FastEthernet0/0` | |   | `R1(config-if)#``no ip address dhcp` | |   | `R1(config-if)#``ip address 192.168.183.133 255.255.255.0` | |   | `R1(config-if)#``speed 100` | |   | `R1(config-if)#``duplex full` | |   | `R1(config-if)#``exit` | |   | `R1(config)#``ip domain-lookup` | |   | `R1(config)#``ip name-server 192.168.183.2` | |   | `R1(config)#``ip route` | |   | `R1(config)#``router ospf 1` | |   | `R1(config-router)#``network 0.0.0.0 255.255.255.255 area 0` | |   | `R1(config-router)#``end` | |   | `R1#``write memory` | |   | 保存配置后,对默认网关(192.168.183.2)和网络上的任何其它设备执行快速 ping 测试。他们应该都可以从 R1 联系到。 | | **5** | 以下是 LAB-SW1 的配置: | |   | 现在打开 LAB-SW1 控制台,重新配置第一台 CML-PERSONAL 交换机。该交换机已经手动配置了 VLAN 1 接口,因此无需重新配置 IP 地址。由于交换机将用作 L2 设备,因此无需配置 DNS 或静态路由。 | |   | `LAB-SW1#` `show ip interface brief | be Vlan1` | |   | `Vlan1                  192.168.183.101 YES NVRAM  up                    up` | |   | `Use the following configuration to complete the LAB-SW1 configuration.` | |   | `LAB-SW1#``configure terminal` | |   | `LAB-SW1(config)#``enable password cisco123` | |   | `LAB-SW1(config)#``username pynetauto privilege 15 password cisco123` | |   | `LAB-SW1(config)#``line vty 0 15` | |   | `LAB-SW1(config-line)#``login local` | |   | `LAB-SW1(config-line)#``transport input all` | |   | `LAB-SW1(config-line)#``logging synchronous` | |   | `LAB-SW1(config-line)#``no exec-timeout` | |   | `LAB-SW1(config-line)#``line console 0` | |   | `LAB-SW1(config-line)#``logging synchronous` | |   | `LAB-SW1(config-line)#``no exec-timeout` | |   | `LAB-SW1(config-line)#``end` | |   | `LAB-SW1#copy running-config startup-config  # Save running-config to startup-config` | |   | `Destination filename [startup-config]?` | |   | `Building configuration...` | |   | `[OK]` | | **6** | 以下是 lab-sw2 的配置: | |   | 让我们对新添加的交换机应用类似的配置,`lab-sw2.`该交换机也将被配置为第 2 层交换机,因此不需要启用 IP 路由或配置 DNS。由于该配置与之前的 LAB-SW2 配置几乎相同,因此不再对其进行解释。 | |   | `Switch>en` | |   | `Switch#` `conf t` | |   | `Switch(config)#` `hostname lab-sw2` | |   | `lab-sw2(config)#` `interface vlan 1` | |   | `lab-sw2(config-if)#` `ip add 196.168.183.102 255.255.255.0` | |   | `lab-sw2(config-if)#` `no shut` | |   | `lab-sw2(config-if)#` `enable password cisco123` | |   | `lab-sw2(config)#` `username pynetauto pri 15 pass cisco123` | |   | `lab-sw2(config)#` `line vty 0 15` | |   | `lab-sw2(config-line)#` `login local` | |   | `lab-sw2(config-line)#` `transport input all` | |   | `lab-sw2(config-line)#` `logging synchronous` | |   | `lab-sw2(config-line)#` `no exec-timeout` | |   | `lab-sw2(config-line)#` `line console 0` | |   | `lab-sw2(config-line)#` `logging synchronous` | |   | `lab-sw2(config-line)#` `no exec-timeout` | |   | `lab-sw2(config-line)#` `end` | |   | `lab-sw2#copy running-config startup-config` | |   | `Destination filename [startup-config]?` | |   | `Building configuration...` | |   | `[OK]` | | **7** | 这是 R1 实验室的配置: | |   | 接下来,打开 R1 实验室的控制台,分配一个 DNS 服务器,然后添加一个静态路由,以便在我们的实验室拓扑上实现顺畅的通信。R1 实验室的 Gi0/0 已经被手动配置了 IP 地址 192.168.183.10/24,所以没有必要配置它。配置第一台 CML 个人路由器,如下所示: | |   | `To configure LAB-R1, use the following configuration:` | |   | `LAB-R1#``configure terminal` | |   | `LAB-R1(config)#``interface GigabitEthernet 0/1` | |   | `LAB-R1(config-if)#``ip address 172.168.1.1 255.255.255.0` | |   | `LAB-R1(config-if)#``no shut` | |   | `LAB-R1(config-if)#``exit` | |   | `LAB-R1(config)#``ip domain-lookup` | |   | `LAB-R1(config)#``ip name-server 192.168.183.2` | |   | `LAB-R1(config)#``ip route 0.0.0.0 0.0.0.0 192.168.183.2` | |   | `LAB-R1(config)#``enable password cisco123` | |   | `LAB-R1(config)#``username pynetauto privilege 15 password cisco123` | |   | `LAB-R1(config)#``line vty 0 15` | |   | `LAB-R1(config-line)#``login local` | |   | `LAB-R1(config-line)#``transport input all` | |   | `LAB-R1(config-line)#``logging synchronous` | |   | `LAB-R1(config-line)#``no exec-timeout` | |   | `LAB-R1(config-line)#``line console 0` | |   | `LAB-R1(config-line)#``logging synchronous` | |   | `LAB-R1(config-line)#``no exec-timeout` | |   | `LAB-R1(config-line)#``end` | |   | `LAB-R1#``copy running-config startup-config` | |   | `Destination filename [startup-config]?` | |   | `Building configuration...` | |   | `[OK]` | | **8** | 以下是 lab-r2 的配置: | |   | 使用以下配置建议配置第二台 CML-PERSONAL L3 路由器。基本配置与 R1 实验室的配置几乎相同,因此不再逐行解释。 | |   | `Router#` `conf t` | |   | `Router(config)#` `hostname lab-r2` | |   | `lab-r2(config-if)#` `interface GigabitEthernet 0/0` | |   | `lab-r2(config-if)#` `ip address 172.168.1.2 255.255.255.0` | |   | `lab-r2(config)#` `interface GigabitEthernet 0/1` | |   | `lab-r2(config-if)#` `ip add 192.168.183.20 255.255.255.0` | |   | `lab-r2(config-if)#` `no shut` | |   | `lab-r2(config-if)#` `exit` | |   | `lab-r2(config)#` `ip domain-lookup` | |   | `lab-r2(config)#` `ip name-server 192.168.183.2` | |   | `lab-r2(config)#` `ip route 0.0.0.0 0.0.0.0 192.168.183.2` | |   | `lab-r2(config)#` `enable password cisco123` | |   | `lab-r2(config)#` `username pynetauto pri  15 password cisco123` | |   | `lab-r2(config)#` `line vty 0 15` | |   | `lab-r2(config-line)#` `login local` | |   | `lab-r2(config-line)#` `transport input all` | |   | `lab-r2(config-line)#` `logging synchronous` | |   | `lab-r2(config-line)#` `no exec-timeout` | |   | `lab-r2(config-line)#` `line console 0` | |   | `lab-r2(config-line)#` `logging synchronous` | |   | `lab-r2(config-line)#` `no exec-timeout` | |   | `lab-r2(config-line)#` `end` | |   | `lab-r2#` `copy running-config startup-config` | |   | `Destination filename [startup-config]?` | |   | `Building configuration...` | |   | `[OK]` | | **9** | One of the background shapes has been changed from an oval to a rectangle to make the lab look better, and the topology has been decorated with IP addresses to make the lab steps clear. Take time to decorate your topology to help you with the IP addressing for the coming labs. See Figure 12-37, Figure 12-38, and Figure 12-39.![img/492721_1_En_12_Fig37_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig37_HTML.jpg)图 12-37。配置 CML 实验室拓扑,三选三![img/492721_1_En_12_Fig38_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig38_HTML.jpg)图 12-38。拓扑摘要窗口![img/492721_1_En_12_Fig39_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_12_Fig39_HTML.jpg)图 12-39。服务器摘要窗口 | | **10** | 现在,SSH 进入`ubutun20s1`或`centos8s1`服务器,向我们拓扑中的每个网络设备发送一些 ICMP 数据包。尝试使用`ping`命令发送一些 ICMP 消息。下面列出了用于 ping 和检查连通性的 IP 地址。如果您使用不同的子网,您的网络 IP 地址可能会不同。 | |   | `lab-r2 Gi0/1` | |   | `[pynetauto@centos8s1 ~]$` `ping 192.168.183.133 -c 2` | |   | `PING 192.168.183.133 (192.168.183.133) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.133: icmp_seq=1 ttl=255 time=7.76 ms` | |   | `64 bytes from 192.168.183.133: icmp_seq=2 ttl=255 time=4.56 ms` | |   | `--- 192.168.183.133 ping statistics ---` | |   | `2 packets transmitted, 2 received, 0% packet loss, time 3ms` | |   | `rtt min/avg/max/mdev = 4.556/6.158/7.761/1.604 ms` | |   | `[pynetauto@centos8s1 ~]$` `ping 192.168.183.129 -c 2` | |   | `PING 192.168.183.129 (192.168.183.129) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.129: icmp_seq=1 ttl=255 time=31.8 ms` | |   | `64 bytes from 192.168.183.129: icmp_seq=2 ttl=255 time=19.6 ms` | |   | `--- 192.168.183.129 ping statistics ---` | |   | `2 packets transmitted, 2 received, 0% packet loss, time 3ms` | |   | `rtt min/avg/max/mdev = 19.562/25.669/31.776/6.107 ms` | |   | `[pynetauto@centos8s1 ~]$` `ping 192.168.183.101 -c 2` | |   | `PING 192.168.183.101 (192.168.183.101) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.101: icmp_seq=1 ttl=255 time=25.0 ms` | |   | `64 bytes from 192.168.183.101: icmp_seq=2 ttl=255 time=24.1 ms` | |   | `--- 192.168.183.101 ping statistics ---` | |   | `2 packets transmitted, 2 received, 0% packet loss, time 2ms` | |   | `rtt min/avg/max/mdev = 24.056/24.547/25.038/0.491 ms` | |   | `[pynetauto@centos8s1 ~]$` `ping 192.168.183.102 -c 2` | |   | `PING 192.168.183.102 (192.168.183.102) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.102: icmp_seq=1 ttl=255 time=35.5 ms` | |   | `64 bytes from 192.168.183.102: icmp_seq=2 ttl=255 time=33.0 ms` | |   | `64 bytes from 192.168.183.20: icmp_seq=1 ttl=255 time=184 ms` | |   | `64 bytes from 192.168.183.20: icmp_seq=2 ttl=255 time=46.10 ms` | |   | `--- 192.168.183.102 ping statistics ---` | |   | `2 packets transmitted, 2 received, 0% packet loss, time 3ms` | |   | `rtt min/avg/max/mdev = 33.049/34.295/35.542/1.260 ms` | |   | `[pynetauto@centos8s1 ~]$` `ping 192.168.183.10 -c 2` | |   | `PING 192.168.183.10 (192.168.183.10) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.10: icmp_seq=1 ttl=255 time=37.8 ms` | |   | `64 bytes from 192.168.183.10: icmp_seq=2 ttl=255 time=20.1 ms` | |   | `--- 192.168.183.10 ping statistics ---` | |   | `2 packets transmitted, 2 received, 0% packet loss, time 3ms` | |   | `rtt min/avg/max/mdev = 20.081/28.936/37.792/8.857 ms` | |   | `[pynetauto@centos8s1 ~]$` `ping 192.168.183.20 -c 2` | |   | `PING 192.168.183.20 (192.168.183.20) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.20: icmp_seq=1 ttl=255 time=184 ms` | |   | `64 bytes from 192.168.183.20: icmp_seq=2 ttl=255 time=46.10 ms` | |   | `--- 192.168.183.20 ping statistics ---` | |   | `2 packets transmitted, 2 received, 0% packet loss, time 3ms` | |   | `rtt min/avg/max/mdev = 46.992/115.296/183.601/68.305 ms` | 现在,您已经完成了 CML L3 路由器映像的集成以及 Python 服务器和拓扑中其他网络设备之间的通信测试。 ## 摘要 本章向您介绍了 Cisco CML 与 GNS3 的集成。您下载了 CML L2 和 L3 映像并将其与 GNS3 集成;然后,您构建了一个基本的 CML 实验室拓扑,并对其进行了快速测试。然后,您确认了 Python automation server 与`cmllab-basic`拓扑中所有路由器和交换机之间的网络连接。您已经完成了 CML 实验准备,现在可以开始编写一些 Python 代码到 Telnet 或 SSH,并在 GNS3 上使用 Cisco 虚拟路由器和交换机。 在第十三章中,您将从 Python Telnet 库开始学习如何从 Python Linux 服务器控制网络设备。 # 十三、Python 网络自动化实验室:基本远程登录 本章专门介绍使用 Python 的`telnetlib`库的 Telnet 实验室。您将学习如何使用一个基本的 Telnet 示例来重复您在键盘上执行的任务,并将其转换为 Python 脚本。尽管 Telnet 是一种不安全的协议,并且最佳实践是使用 SSH 协议进行远程连接,但它现在仍在许多生产环境中使用,这些环境位于一个安全的网络中,只有少数工程师可以访问该环境。在本实验结束时,您将能够使用基本的 Telnet 库执行以下操作:在 Python 解释器会话中与网络设备进行交互,使用使用循环的模板化方法配置单交换机和多交换机 VLAN,执行基本的网络工程师管理任务,例如通过外部文件源添加用户,以及将`running-config`或`startup-configs`备份到本地 Python 服务器目录。在下一章,我们将使用 Python SSH 库来扩展本章所学的思想。 ![img/492721_1_En_13_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_13_Figa_HTML.jpg) ## Python 网络自动化:Telnet 实验室 在本书中,我们投入了大量时间来使实验室体验尽可能接近真实的物理设备实验室,这样您就可以在没有硬件或担心不同连接器类型的情况下获得真实的实验室体验。在主机上的当前实验室设置下,您可以在三种不同的操作系统上编写代码:Windows 10 上的主机、CentOS 8.1 Linux server 和 Ubuntu 20.04 LTS Linux VM server。如果您想添加更多的地方来编写和测试代码,您可以克隆任何 Linux 虚拟机。在第二章中,向您介绍了 Windows 操作系统上的 Python 3,然后在第 4 、 5 和 6 章中,您完成了 Linux 命令行界面(CLI)上的大部分练习。公司几乎从不在 Windows 服务器上安装 Python 并在生产环境中使用它。换句话说,绝大多数 Python 应用运行在安全加固的 Linux 服务器上。虽然微软已经引入了 Linux 的 Windows 子系统(WSL)的概念,并且它可以在 Windows 上运行 Linux 系统,并且非常容易使用,但是在 Windows 上运行的实际虚拟机是 Linux OS。 目前网络自动化的热潮是为了更少的工程师,他们可以使用应用编程接口(API)做更多的工作,而不用打开 CLI 控制台、Telnet 或 SSH 终端窗口。跨许多较新的网络平台引入 API 支持也大大减少了网络行业中图形用户界面(GUI)的使用。然而,业内人士表示,登录网络设备并管理它们的传统方式将在未来许多年内保持不变。我们仍然使用键盘来编写 Python 代码,并调用代码中使用的 API 库,但除非我们是 API 开发人员,否则我们可能不会完全理解每个 API 的内部工作方式。归根结底,网络自动化是模仿有经验的网络工程师的思维方式。这种产品的一个很好的例子是 Red Hat 的 Ansible,它依赖于无代理的 SSH 连接,而不是 API。从这个意义上来说,API 是对这一点的重新解释,但是是为了不使用键盘和屏幕的机器对机器的交互。在本章中,您将开始编写 Python Telnet 脚本,以便与我们的基本实验拓扑中的网络设备进行交互。尽管使用 API 正在成为管理企业网络设备的新标准,但是在现在和可预见的将来,仍然会有许多设备没有 API 接口,您仍然需要使用 Telnet 或 SSH 连接来远程连接到这些设备。这意味着我们仍然可以在网络自动化中使用 Python,依赖于 Telnet 和 SSH 库。反正写 Python 代码的必备技能不变;只有访问方法和库发生了变化。见图 13-1 。 ![img/492721_1_En_13_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_13_Fig1_HTML.jpg) 图 13-1。 CML lab-基本逻辑拓扑 在高度安全的客户端环境中,您只能从名为 *jump hosts* 的指定服务器管理核心网络和系统设备。跳转主机可以基于 Windows 或 Linux 操作系统。`cmllab-basic` GNS3 拓扑模拟这种环境,您必须在您的主机 PC 上使用 PuTTY SSH 到`ubuntu20s1` (Python 和 jump host)服务器来管理网络中的设备。`centos8s1`服务器提供我们在各种实验室场景中所依赖的 IP 服务。我们将在 Windows Notepad++中编写 Python 脚本,或者通过 SSH 会话在`ubuntu20s1`服务器的文本编辑器中直接输入脚本。对于打字又快又准的人,可以直接把 Python 脚本输入 Ubuntu 服务器。不过,如果你喜欢使用 Windows 记事本或 Notepad++中的脚本,然后剪切并粘贴到 Ubuntu 服务器上,这也是一个可以接受的方法。让我们启动`cmllab-basic`项目中的所有设备,编写一些 Python 代码,看看 Telnet Python 脚本如何与 Cisco 路由器和交换机交互。 ## 远程登录实验 1:在 Python 解释器上与 Cisco 设备进行交互式远程登录会话 对于本实验,您需要启动`ubuntu20s1` Linux 服务器(192.168.183.132)】、`LAB-SW1` (192.168.183.101)和`LAB-R1` (192.168.183.10)。运行第一个脚本后,您还将启动 IOS 路由器`R1` (192.168.183.133),检查 OSPF 邻居关系。如果您使用另一个子网连接到您的 NAT ( `VMnetwork8`),您的 IP 地址将与本书不同,您必须更换 IP 地址。见图 13-2 。 ![img/492721_1_En_13_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_13_Fig2_HTML.jpg) 图 13-2。 Telnet 实验 1,正在使用的设备 您将编写一个 Telnet 脚本,并运行它从`ubuntu20s1` Python 服务器远程登录到`LAB-R1`路由器,以进行配置更改。在本实验结束时,`running-config`文件中的`LAB-R1`必须具有以下配置: | # | 工作 | | --- | --- | | one | 在您的主机 PC 上打开 PuTTY,并通过 SSH 进入`ubuntu20s1` (192.168.183.132)服务器。如果您分配了不同的 IP 地址,请使用您的服务器 IP 地址。如果您忘记了哪个 IP 地址分配给了`ubuntu20s1`服务器,请从 VMware Workstation 打开 Linux 控制台并登录;然后运行`ip address`命令。 | |   | 现在用你的用户名和密码登录。 | |   | `login as:` `pynetauto` | |   | `pynetauto@192.168.183.132's password: *********` | |   | `Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-47-generic x86_64)` | |   | `* Documentation: https://help.ubuntu.com` | |   | `* Management: https://landscape.canonical.com` | |   | `* Support: https://ubuntu.com/advantage` | |   | `System information disabled due to load higher than 1.0` | |   | `327 updates can be installed immediately.` | |   | `142 of these updates are security updates.` | |   | `To see these additional updates run: apt list --upgradable` | |   | `Last login: Fri Jan  8 23:26:01 2021 from 192.168.183.1` | |   | `pynetauto@ubuntu20s1:~$` | | Two | 目前,要从`ubuntu20s1`运行 Python,我们必须发出`python3`命令。由于没有安装 Python 2.7,Python 3 是您在这里将要使用的唯一版本,您可以使用一个`alias`命令将`python3`缩短为`python`。按照这里的说明在 Linux 中使用别名: | |   | `pynetauto@ubuntu20s1:~$` `users` | |   | `pynetauto` | |   | `pynetauto@ubuntu20s1:~$` `pwd` | |   | `/home/pynetauto` | |   | `pynetauto@ubuntu20s1:~$` `nano /home/pynetauto/.bashrc` | |   | 打开`/home/user/`目录中的`.bashrc`文件后,在`.bashrc`文件中添加一个新行`alias python=python3`;这与图 13-1 类似。添加完一行后,按 Ctrl+X 保存并退出文件。 | |   | `GNU nano 4.8                                       /home/pynetauto/.bashrc` | |   | `# alias ADDED by pynetauto` | |   | `alias python=python3` | |   | `^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` | | three | 添加别名行后,运行`source ~/.bashrc`命令重启用户的`bashrc`。您现在可以使用`python`或`python3`命令在您的`ubuntu20s1`服务器上运行 Python 3.8.2。 | |   | `pynetauto@ubuntu20s1:~$` `source ~/.bashrc` | |   | `pynetauto@ubuntu20s1:~$` `python` | |   | `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.` | |   | `>>>` | | four | 正如我们在 IOS Telnet 实验中所做的那样,转到 [`https://docs.python.org/3/library/telnetlib.html`](https://docs.python.org/3/library/telnetlib.html) 并向下滚动到页面底部。找到一个示例 Telnet 代码。你也可以重用第十章 8 的`R1` IOS 实验室的旧代码。让我们复制这段代码来制作我们的第一个 Telnet 脚本。该代码将在整个 Telnet 实验中使用,因此这里快速解释了给定 Python 脚本中每一行的作用。由于我们使用的是 Cisco 设备,所以提供了更多关于 Cisco Telnet 会话的解释。 | |   | **远程登录示例** | |   | A simple example illustrates the typical use : | `import getpass` | # Getpass module for importing passwords | | `import telnetlib` | # Import telnet Lib module | | `HOST = "localhost"` | # for Telnet IP address or host name of the device to be connected | | `user = input("Enter your remote account: ")` | # User input | | `password = getpass.getpass()` | # The variable password is defined as getpass.getpass function class | | `tn = telnetlib.Telnet(HOST)` | # The variable tn is defined as telnetlib. Telnet type category | | `tn.read_until(b"login: ")` | # "log in:" appears in the telnet window, and Python will wait until "login:" On Cisco devices, the binary information expected to be returned is "user name", which needs to be updated in your script. | | `tn.write(user.encode('ascii') + b"\n")` | # Encode userID in ASCII and send it to telnet virtual terminal | | `if password:` | # If you are prompted to enter the password | | `tn.read_until(b"Password: ")` | # Wait for the password | | `tn.write(password.encode('ascii') + b"\n")` | # The password encoded in ASCII and sent to telnet virtual terminal | | `tn.write(b"ls\n")` | #' ls' is the Linux command list. Ls is not used in Cisco devices. Can be deleted or modified. | | `tn.write(b"exit\n")` | # Send "Exit" command | | `print(tn.read_all().decode('ascii'))` | # to telnet virtual terminal, read all commands running in this session, decode them in ASCII and print them out on the user screen. | | | five | 创建一个名为`telnet_labs`的目录,更改工作目录,然后使用`touch`命令创建第一个 CML Telnet 脚本。剧本名字叫`add_lo_ospf1.py`。请遵循以下步骤: | |   | `pynetauto@ubuntu20s1:~$` `pwd` | |   | `/home/pynetauto` | |   | `pynetauto@ubuntu20s1:~$` `mkdir telnet_labs` | |   | `pynetauto@ubuntu20s1:~$` `cd telnet_labs` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$ pwd` | |   | `/home/pynetauto/telnet_labs` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `touch add_lo_ospf1.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ls` | |   | `7.2.1_add_lo_ospf1.py` | | six | 为了加快实验速度,请使用 Linux 服务器上的 nano 文本编辑器。首先,使用`nano add_lo_ospf1.py`打开空白 Python 脚本。然后,使用右键菜单复制并粘贴 Telnet 示例。最后,修改以下源代码中突出显示的部分: | |   | `GNU nano 4.8` `add_lo_ospf1.py` | |   | `import getpass` | |   | `import telnetlib` | |   | `HOST = "192.168.183.10"` | |   | `user = input("Enter your username: ")` | |   | `password = getpass.getpass()` | |   | `tn = telnetlib.Telnet(HOST)` | |   | `tn.read_until(b"Username: ")` | |   | `tn.write(user.encode('ascii') + b"\n")` | |   | `if password:` | |   | `tn.read_until(b"Password: ")` | |   | `tn.write(password.encode('ascii') + b"\n")` | |   | `tn.write(b"enable\n")` | |   | `tn.write(b"cisco123\n")` | |   | `tn.write(b"conf t\n")` | |   | `tn.write(b"int loopback 0\n")` | |   | `tn.write(b"ip add 2.2.2.2 255.255.255.255\n")` | |   | `tn.write(b"int loopback 1\n")` | |   | `tn.write(b"ip add 4.4.4.4 255.255.255.255\n")` | |   | `tn.write(b"router ospf 1\n")` | |   | `tn.write(b"network 0.0.0.0 255.255.255.255 area 0\n")` | |   | `tn.write(b"end\n")` | |   | `tn.write(b"exit\n")` | |   | `print(tn.read_all().decode('ascii'))` | |   | `^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` | | seven | 在从`ubuntu20s1`服务器运行之前的脚本之前,转到`LAB-R1`并启用`debug telnet`来监控来自`LAB-R1`的 Telnet 活动。 | |   | `LAB-R1#` `debug telnet` | |   | `Incoming Telnet debugging is on` | | eight | 在`ubuntu20s1`上,使用`python add_lo_ospf1.py`命令运行脚本。当提示输入用户名和密码时,输入您的路由器用户名和密码。一旦脚本成功运行,您的屏幕应该类似于以下内容: | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `python add_lo_ospf1.py` | |   | `Enter your username:` `pynetauto` | |   | `Password: **********` | |   | `**************************************************************************` | |   | `* IOSv is strictly limited to use for evaluation, demonstration and IOS  *` | |   | `* education. IOSv is provided as-is and is not supported by Cisco's      *` | |   | `* Technical Advisory Center. Any use or disclosure, in whole or in part, *` | |   | `* of the IOSv Software or Documentation to any third party for any       *` | |   | `* purposes is expressly prohibited except as otherwise authorized by     *` | |   | `* Cisco in writing.                                                      *` | |   | `**************************************************************************` | |   | `LAB-R1#enable` | |   | `LAB-R1#cisco123` | |   | `Translating "cisco123"...domain server (192.168.183.2)` | |   | `(192.168.183.2)` | |   | `Translating "cisco123"...domain server (192.168.183.2)` | |   | `% Bad IP address or host name` | |   | `% Unknown command or computer name, or unable to find computer address` | |   | `LAB-R1#conf t` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `LAB-R1(config)#int loopback 0` | |   | `LAB-R1(config-if)#ip add 2.2.2.2 255.255.255.255` | |   | `LAB-R1(config-if)#int loopback 1` | |   | `LAB-R1(config-if)#ip add 4.4.4.4 255.255.255.255` | |   | `LAB-R1(config-if)#router ospf 1` | |   | `LAB-R1(config-router)#network 0.0.0.0 255.255.255.255 area 0` | |   | `LAB-R1(config-router)#end` | |   | `LAB-R1#exit` | | nine | 转到`LAB-R1`,现在检查控制台。您可以看到刚刚发生的 Telnet 活动。 | |   | `LAB-R1#` | |   | `*Jan 11 18:11:08.885: Telnet578: 1 1 251 1` | |   | `*Jan 11 18:11:08.887: TCP578: Telnet sent WILL ECHO (1)` | |   | `[...omitted for brevity]` | |   | `*Jan 11 18:11:09.181: TCP578: Telnet received WONT TTY-TYPE (24)` | |   | `*Jan 11 18:11:09.183: TCP578: Telnet received WONT WINDOW-SIZE (31)` | |   | `LAB-R1#` | |   | `*Jan 11 18:11:11.730: %LINEPROTO-5-UPDOWN: Line protocol on Interface Loopback0, changed state to up` | |   | `LAB-R1#` | |   | `*Jan 11 18:11:12.972: %LINEPROTO-5-UPDOWN: Line protocol on Interface Loopback1, changed state to up` | |   | `*Jan 11 18:11:13.791: %SYS-5-CONFIG_I: Configured from console by pynetauto on vty0 (192.168.183.132)` | |   | `LAB-R1#` | | Ten | 从`LAB-R1`控制台,运行`show ip interface brief`命令检查新配置的`Loopback0`和`Loopback1`配置。 | |   | `LAB-R1#` `show ip interface brief` | |   | `Interface                  IP-Address      OK? Method Status                Protocol` | |   | `GigabitEthernet0/0         192.168.183.10  YES manual up                    up` | |   | `GigabitEthernet0/1         172.168.1.1     YES manual up                    up` | |   | `GigabitEthernet0/2         unassigned      YES unset  administratively down down` | |   | `GigabitEthernet0/3         unassigned      YES unset  administratively down down` | |   | `Loopback0                  2.2.2.2         YES manual up                    up` | |   | `Loopback1                  4.4.4.4         YES manual up                    up` | | Eleven | 在 IOS 路由器`R1`上,打开`R1`的电源,观察`R1`和`LAB-R1`路由器之间是否正确形成 OSPF 邻居关系。一旦 OSPF 状态从`LOADING`变为`FULL, Loading Done`,运行`show ip ospf neighbor`命令检查邻居关系。在这个阶段,你应该能够从`R1`ping`LAB-R`1 的`Loopback0`(2.2.2.2)和`Loopback1`(4.4.4.4)接口。 | |   | `R1#` | |   | `*Mar  1 00:21:25.807: %OSPF-5-ADJCHG: Process 1, Nbr 4.4.4.4 on FastEthernet0/0 from LOADING to FULL, Loading Done` | |   | `R1#` `show ip ospf neighbor` | |   | `Neighbor ID     Pri   State           Dead Time   Address         Interface` | |   | `4.4.4.4           1   FULL/DR         00:00:39    192.168.183.10  FastEthernet0/0` | |   | `R1#` `ping 2.2.2.2` | |   | `Type escape sequence to abort.` | |   | `Sending 5, 100-byte ICMP Echos to 2.2.2.2, timeout is 2 seconds:` | |   | `!!!!!` | |   | `Success rate is 100 percent (5/5), round-trip min/avg/max = 8/11/20 ms` | |   | `R1#` `ping 4.4.4.4` | |   | `Type escape sequence to abort.` | |   | `Sending 5, 100-byte ICMP Echos to 4.4.4.4, timeout is 2 seconds:` | |   | `!!!!!` | |   | `Success rate is 100 percent (5/5), round-trip min/avg/max = 8/13/28 ms` | | Twelve | 在`LAB-R1`时刻,您将观察到 OSPF 状态从`LOADING`变为`FULL, Loading Done`。运行`show ip ospf neighbor`命令检查与`R1`的邻居关系。在此阶段,您应该能够 ping 通 R1 的 f0/1 接口(7.7.7.2)。运行`undebug all`命令关闭 Telnet 调试并完成您的第一个 CML-PERSONAL Telnet 实验。 | |   | `LAB-R1#` | |   | `*Jan 11 18:11:21.417: %OSPF-5-ADJCHG: Process 1, Nbr 192.168.183.133 on GigabitEthernet0/0 from LOADING to FULL, Loading Done` | |   | `LAB-R1#` `show ip ospf neighbor` | |   | `Neighbor ID     Pri   State           Dead Time   Address         Interface` | |   | `192.168.183.133   1   FULL/BDR        00:00:39    192.168.183.133 GigabitEthernet0/0` | |   | `LAB-R1#` `ping 7.7.7.2` | |   | `Type escape sequence to abort.` | |   | `Sending 5, 100-byte ICMP Echos to 7.7.7.2, timeout is 2 seconds:` | |   | `!!!!!` | |   | `Success rate is 100 percent (5/5), round-trip min/avg/max = 7/10/14 ms` | |   | `LAB-R1#` `undebug all` | |   | `All possible debugging has been turned off` | * IP 地址为 2.2.2.2/32 的环回 0 * IP 地址为 4.4.4.4/32 的环回 1 * OSPF 1 的网络 0.0.0.0 255.255.255.255 位于区域 0 所有用到的 Python 代码都可以从 [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) 下载。如果您喜欢第一个 Telnet 实验,现在想尝试第二个实验,让我们继续。 ## Telnet 实验 2:使用 Python Telnet 模板配置单台交换机 在本实验中,您将通过 Telnet 在`LAB-SW1`上配置一些 VLANs。在本实验中,您需要启动`ubuntu20s1` Linux 服务器(192.168.183.132)和`LAB-SW1` (192.168.183.101)。为了节省时间,我们将复制第一个脚本`add_lo_ospf1.py`,并将其重命名为`add_vlans_single.py`。本实验结束时,`LAB-SW1`应如图 13-3 所示进行配置。 ![img/492721_1_En_13_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_13_Fig3_HTML.jpg) 图 13-3。 Telnet 实验 2,正在使用的设备 | # | 工作 | | --- | --- | | **1** | SSH 进入`ubuntu20s1`服务器(192.168.183.132 ),继续在第一个实验的目录中工作。按照这里的说明复制第一个 Python 脚本,并将其重命名为`add_vlans_single.py`。 | |   | `pynetauto@ubuntu20s1:~$` `cd telnet_labs` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ls` | |   | `7.2.1_add_lo_ospf1.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `cp add_lo_ospf1.py add_vlans_single.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ls` | |   | `add_lo_ospf1.py  add_vlans_single.py` | | **2** | 用 nano 文本编辑器打开新创建的`.py`文件。按 Ctrl+K 复制/剪切代码行,按 Ctrl+U 粘贴信息。注意`LAB-SW1`的 IP 地址是 192.168.183.101。确保用这个 IP 地址更新`HOST`旁边的 IP 地址。 | | 使用以下信息修改您的脚本。编写代码时,每个逗号和句号都很重要。`b` =字节文字量;生成`byte`类型的实例,而不是`str`类型的实例。`\n` =转到下一行的换行符。换句话说,`"[Enter] key"encode('ascii')`意味着对交换机/路由器使用 ASCII 编码。 | |   | `GNU nano 4.8` `add_vlans_single.py` | |   | `import getpass` | |   | `import telnetlib` | |   | `HOST = "``192.168.183.101` | |   | `user = input("Enter your username: ")` | |   | `password = getpass.getpass()` | |   | `tn = telnetlib.Telnet(HOST)` | |   | `tn.read_until(b"Username: ")` | |   | `tn.write(user.encode('ascii') + b"\n")` | |   | `if password:` | |   | `tn.read_until(b"Password: ")` | |   | `tn.write(password.encode('ascii') + b"\n")` | |   | `# Get into config mode` | |   | `tn.write(b"conf t\n")` | |   | `# configure 4 VLANs with VLAN names` | |   | `tn.write(b"vlan 2\n")` | |   | `tn.write(b"name Data_vlan_2\n")` | |   | `tn.write(b"vlan 3\n")` | |   | `tn.write(b"name Data_vlan_3\n")` | |   | `tn.write(b"vlan 4\n")` | |   | `tn.write(b"name Voice_vlan_4\n")` | |   | `tn.write(b"vlan 5\n")` | |   | `tn.write(b"name Wireless_vlan_5\n")` | |   | `tn.write(b"exit\n")` | |   | `# configure Gi1/0 - Gi1/3 as access siwtchports and assign vlan 5 for wireless APs` | |   | `tn.write(b"interface range gi1/0 - 3\n")` | |   | `tn.write(b"switchport mode access\n")` | |   | `tn.write(b"switchport access vlan 5\n")` | |   | `tn.write(b"no shut\n")` | |   | `#configure gi2/0 - gi2/3 as access switchports and assign vlan 2 for data and vlan 4 for voice` | |   | `tn.write(b"interface range gi2/0 - 3\n")` | |   | `tn.write(b"switchport mode access \n")` | |   | `tn.write(b"switchport access vlan 2\n")` | |   | `tn.write(b"switchport voice vlan 4\n")` | |   | `tn.write(b"no shut\n")` | |   | `tn.write(b"end\n")` | |   | `tn.write(b"exit\n")` | |   | `print(tn.read_all().decode('ascii'))` | |   | `^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` | | **3** | 从`ubuntu20s1`服务器,运行`ping 192.168.183.101 –c 4`命令来检查连接。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ping 192.168.183.101 -c 3` | |   | `PING 192.168.183.101 (192.168.183.101) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.101: icmp_seq=1 ttl=255 time=20.3 ms` | |   | `64 bytes from 192.168.183.101: icmp_seq=2 ttl=255 time=7.77 ms` | |   | `64 bytes from 192.168.183.101: icmp_seq=3 ttl=255 time=17.5 ms` | |   | `--- 192.168.183.101 ping statistics ---` | |   | `4 packets transmitted, 4 received, 0% packet loss, time 3005ms` | |   | `rtt min/avg/max/mdev = 7.773/14.035/20.312/5.067 ms` | | **4** | 或者,在`LAB-SW1`上,启用`debug telnet`在脚本运行期间捕获 Telnet 活动。打开调试后,保持控制台窗口打开。 | |   | `LAB-SW1#` `debug telnet` | | **5** | 现在我们检查了连通性。让我们运行`python add_vlans_single.py`命令来运行脚本,添加新的 VLANs,并配置指定的交换机端口。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `python add_vlans_single.py` | |   | `Enter your username:` `pynetauto` | |   | `Password:********` | |   | `**************************************************************************` | |   | `* IOSv is strictly limited to use for evaluation, demonstration and IOS  *` | |   | `* education. IOSv is provided as-is and is not supported by Cisco's      *` | |   | `* Technical Advisory Center. Any use or disclosure, in whole or in part, *` | |   | `* of the IOSv Software or Documentation to any third party for any       *` | |   | `* purposes is expressly prohibited except as otherwise authorized by     *` | |   | `* Cisco in writing.                                                      *` | |   | `**************************************************************************` | |   | `LAB-SW1#conf t` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `LAB-SW1(config)#vlan 2` | |   | `LAB-SW1(config-vlan)#name Data_vlan_2` | |   | `LAB-SW1(config-vlan)#vlan 3` | |   | `LAB-SW1(config-vlan)#name Data_vlan_3` | |   | `LAB-SW1(config-vlan)#vlan 4` | |   | `LAB-SW1(config-vlan)#name Voice_vlan_4` | |   | `LAB-SW1(config-vlan)#vlan 5` | |   | `LAB-SW1(config-vlan)#name Wireless_vlan_5` | |   | `LAB-SW1(config-vlan)#exit` | |   | `LAB-SW1(config)#interface range gi1/0 - 3` | |   | `LAB-SW1(config-if-range)#switchport mode access` | |   | `LAB-SW1(config-if-range)#switchport access vlan 5` | |   | `LAB-SW1(config-if-range)#no shut` | |   | `LAB-SW1(config-if-range)#interface range gi2/0 - 3` | |   | `LAB-SW1(config-if-range)#switchport mode access` | |   | `LAB-SW1(config-if-range)#switchport access vlan 2` | |   | `LAB-SW1(config-if-range)#switchport voice vlan 4` | |   | `LAB-SW1(config-if-range)#no shut` | |   | `LAB-SW1(config-if-range)#end` | |   | `LAB-SW1#exit` | | **6** | 检查`LAB-SW1`控制台窗口以获取调试信息。 | |   | `LAB-SW1#` | |   | `*Jan 11 19:24:31.981: Telnet2: 1 1 251 1` | |   | `*Jan 11 19:24:31.982: TCP2: Telnet sent WILL ECHO (1)` | |   | `*Jan 11 19:24:31.982: Telnet2: 2 2 251 3` | |   | `*Jan 11 19:24:31.984: TCP2: Telnet sent WILL SUPPRESS-GA (3)` | |   | `*Jan 11 19:24:31.984: Telnet2: 80000 80000 253 24` | |   | `*Jan 11 19:24:31.985: TCP2: Telnet sent DO TTY-TYPE (24)` | |   | `*Jan 11 19:24:31.985: Telnet2: 10000000 10000000 253 31` | |   | `*Jan 11 19:24:31.986: TCP2: Telnet sent DO WINDOW-SIZE (31)` | |   | `*Jan 11 19:24:32.036: TCP2: Telnet received DONT ECHO (1)` | |   | `*Jan 11 19:24:32.037: TCP2: Telnet sent WONT ECHO (1)` | |   | `*Jan 11 19:24:32.042: TCP2: Telnet received DONT SUPPRESS-GA (3)` | |   | `*Jan 11 19:24:32.043: TCP2: Telnet sent WONT SUPPRESS-GA (3)` | |   | `LAB-SW1#` | |   | `*Jan 11 19:24:32.048: TCP2: Telnet received WONT TTY-TYPE (24)` | |   | `*Jan 11 19:24:32.050: TCP2: Telnet sent DONT TTY-TYPE (24)` | |   | `*Jan 11 19:24:32.054: TCP2: Telnet received WONT WINDOW-SIZE (31)` | |   | `*Jan 11 19:24:32.054: TCP2: Telnet sent DONT WINDOW-SIZE (31)` | |   | `*Jan 11 19:24:32.186: TCP2: Telnet received DONT ECHO (1)` | |   | `*Jan 11 19:24:32.187: TCP2: Telnet received DONT SUPPRESS-GA (3)` | |   | `*Jan 11 19:24:32.187: TCP2: Telnet received WONT TTY-TYPE (24)` | |   | `*Jan 11 19:24:32.189: TCP2: Telnet received WONT WINDOW-SIZE (31)` | |   | `LAB-SW1#` | |   | `*Jan 11 19:24:40.915: %SYS-5-CONFIG_I: Configured from console by pynetauto on vty0 (192.168.183.132)` | | **7** | 现在检查配置的 VLANs 和`LAB-SW1`交换机上的交换机端口。使用`show vlan`、`show run`和`show ip interface brief`检查开关配置变化。这里显示的是`show vlan`的例子: | |   | `LAB-SW1#show vlan` | |   | `VLAN Name                             Status    Ports` | |   | `---- -------------------------------- --------- -------------------------------` | |   | `1    default                          active    Gi0/0, Gi0/1, Gi0/2, Gi0/3` | |   | `Gi3/0, Gi3/1, Gi3/2, Gi3/3` | |   | `2    Data_vlan_2                      active    Gi2/0, Gi2/1, Gi2/2, Gi2/3` | |   | `3    Data_vlan_3                      active` | |   | `4    Voice_vlan_4                     active    Gi2/0, Gi2/1, Gi2/2, Gi2/3` | |   | `5    Wireless_vlan_5                  active    Gi1/0, Gi1/1, Gi1/2, Gi1/3` | |   | `1002 fddi-default                     act/unsup` | |   | `[...omitted for brevity]` | |   | `------- --------- ----------------- ------------------------------------------` | * 使用以下 VLAN 描述配置 VLANs 2 到 VLAN 5: * VLAN 2, `Data_vlan_2` * VLAN 3, `Data_vlan_3` * VLAN 4, `Voice_vlan_4` * VLAN 5, `Wireless_vlan_5` * 用`Wireless_vlan_5`将千兆以太网 1/0 到千兆以太网 1/3 范围配置为接入交换机端口。 * 用`Data_vlan_2`(数据 VLAN)和`voice_vlan_4`(辅助 VLAN)配置千兆以太网 2/0 到千兆以太网 2/3 范围作为交换机端口。 * 用`no shut`命令调出所有已配置的接口。 您已经成功添加了四个 VLAN,并在各自的 VLAN 中配置了八个交换机端口。现在,我们来看看如何在实验 3 和实验 4 中使用环路添加多个随机 VLANs。 ## Telnet 实验 3:使用 for 循环配置随机 VLANs 在上一个实验中,我们用多行代码配置了 VLANs 2 到 VLAN 5;在这里,您将练习如何使用一个`for`循环用更少的代码添加 VLANs。为了简单起见,我们在这个实验中只添加了五个随机 VLANs,但是我们使用了一个`for`循环,您可以节省时间和必须编写的代码行数。 按照步骤通过 Telnet 在`LAB-SW1`上配置随机 VLANs 101、202、303、404 和 505。在本实验中,您可以继续使用`ubuntu20s1` Linux 服务器(192.168.183.132)和`LAB-SW1` (192.168.183.101)。复制`7.2.2_add_vlans_single.py`文件,并将其重命名为`7.2.3_add_vlans_for_loop.py`,用于本实验。本实验结束时,`LAB-SW1`应如图 13-4 所示进行配置。 ![img/492721_1_En_13_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_13_Fig4_HTML.jpg) 图 13-4。 Telnet 实验 3:使用中的设备 * 将具有以下 VLAN 描述的 VLANs 101、202、303、404 和 505 添加到`LAB-SW1`: * VLAN 101, `Data_vlan_101` * VLAN 202, `Data_vlan_202` * VLAN 303, `Voice_vlan_303` * VLAN 404, `Wireless_vlan_404` * VLAN 505, `Wireless_vlan_505` ![img/492721_1_En_13_Figc_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_13_Figc_HTML.jpg)在本实验中,试着专注于`for`循环是如何工作的,以及它在本场景中是如何使用的。循环被设计来重复执行相同的任务,现在你正在挖掘编程的真正力量。学习 Python 概念,以便将它们应用到您的工作中是您想要达到的目标。祝你好运! | # | 工作 | | --- | --- | | **1** | 首先,移动到`telnet_labs`目录,用`cp`命令复制`add_vlans_single.py`,重命名为`add_vlans_for_loop.py`。命令如下所示: | |   | `pynetauto@ubuntu20s1:~$` `cd telnet_labs` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ls` | |   | `7.2.1_add_lo_ospf1.py  7.2.2_add_vlans_single.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `cp add_vlans_single.py add_vlans_for_loop.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ls` | |   | `add_lo_ospf1.py  add_vlans_single.py  add_vlans_for_loop.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `nano add_vlans_for_loop.py` | | **2** | 现在,修改脚本,如下所示。再次按 Ctrl+K 剪切/复制信息,按 Ctrl+U 粘贴复制的信息。在此更改过程中,请检查您的语法和空白。每个逗号和空格都很重要,尤其是代码块的缩进或四个空格。 | |   | `GNU nano 4.8` `add_vlans_for_loop.py` | |   | `#!/usr/bin/env python3 # This is called the shebang line; Python ignores this line, but the Linux Operating System can read this line and knows which application to run the .py file with.` | |   | `import getpass` | |   | `import telnetlib` | |   | `HOST``= "``192.168.183.101` | |   | `user = input("Enter your username: ")` | |   | `password = getpass.getpass()` | |   | `tn = telnetlib.Telnet(HOST)` | |   | `tn.read_until(b"Username: ")` | |   | `tn.write(user.encode('ascii') + b"\n")` | |   | `if password:` | |   | `tn.read_until(b"Password: ")` | |   | `tn.write(password.encode('ascii') + b"\n")` | |   | `# Get into config mode` | |   | `tn.write(b"conf t\n")` | |   | `# Adds 5 vlans to the list with for loop` | |   | `vlans = [101, 202, 303, 404, 505]` `# vlans to add in  a list` | |   | `for i in vlans` `: # call (index) each item from list vlans` | |   | `command_1 = "vlan " + str(i) + "\n"` `# concatenate first command` | |   | `tn.write(command_1.encode('ascii'))` `# send command_1 with ASCII encoding` | |   | `command_2 = "name PYTHON_VLAN_" + str(i) + "\n"` `# concatenate second command` | |   | `tn.write(command_2.encode('ascii'))` `# send command_2 with ASCII encoding` | |   | `tn.write(b"end\n")` | |   | `tn.write(b"exit\n")` | |   | `print("exiting")` | |   | `print(tn.read_all().decode('ascii'))` | |   | `^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` | |   | 如你所见,`telnetlib`的编解码比较乱;这是因为`telnetlib`使用 pyNUT 与网络设备通信,pyNUT 代码使用字符串文字在内部格式化字符串。PyNUT 是为 Python 2 编写的,`telnetlib`希望大部分输入都是以字节为单位的。在 Python 2 中,`str` (string)类型是一个字节字符串,但是在 Python 3 中,它全部是 Unicode。幸运的是,SSH Python 应用中的编码和解码并不复杂。 | | **3** | 使用以下 Python 命令运行脚本。您可以使用任一命令。我们添加了用户的别名`/.bashrc`,以便使用 Python3 或 Python 来运行脚本。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `python add_vlans_for_loop.py` | |   | `OR` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `python3 add_vlans_for_loop.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `python add_vlans_for_loop.py` | |   | `Enter your username:` `pynetauto` | |   | `Password:********` | |   | `exiting` | |   | `**************************************************************************` | |   | `* IOSv is strictly limited to use for evaluation, demonstration and IOS  *` | |   | `* education. IOSv is provided as-is and is not supported by Cisco's      *` | |   | `* Technical Advisory Center. Any use or disclosure, in whole or in part, *` | |   | `* of the IOSv Software or Documentation to any third party for any       *` | |   | `* purposes is expressly prohibited except as otherwise authorized by     *` | |   | `* Cisco in writing.                                                      *` | |   | `**************************************************************************` | |   | `LAB-SW1#conf t` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `LAB-SW1(config)#vlan 101` | |   | `LAB-SW1(config-vlan)#name PYTHON_VLAN_101` | |   | `LAB-SW1(config-vlan)#vlan 202` | |   | `LAB-SW1(config-vlan)#name PYTHON_VLAN_202` | |   | `LAB-SW1(config-vlan)#vlan 303` | |   | `LAB-SW1(config-vlan)#name PYTHON_VLAN_303` | |   | `LAB-SW1(config-vlan)#vlan 404` | |   | `LAB-SW1(config-vlan)#name PYTHON_VLAN_404` | |   | `LAB-SW1(config-vlan)#vlan 505` | |   | `LAB-SW1(config-vlan)#name PYTHON_VLAN_505` | |   | `LAB-SW1(config-vlan)#end` | |   | `LAB-SW1#exit` | | **4** | 在`LAB-SW1`上运行`show vlan`命令以确认新配置的 VLANs。如果您可以在交换机的 vlan 表中看到新的 VLAN,那么您的 Python 脚本使用 for 循环向交换机添加随机 VLAN。 | |   | `LAB-SW1#` `show vlan` | |   | `VLAN Name                             Status    Ports` | |   | `---- -------------------------------- --------- -------------------------------` | |   | `1    default                          active    Gi0/0, Gi0/1, Gi0/2, Gi0/3` | |   | `Gi3/0, Gi3/1, Gi3/2, Gi3/3` | |   | `2    Data_vlan_2                      active    Gi2/0, Gi2/1, Gi2/2, Gi2/3` | |   | `3    Data_vlan_3                      active` | |   | `4    Voice_vlan_4                     active    Gi2/0, Gi2/1, Gi2/2, Gi2/3` | |   | `5    Wireless_vlan_5                  active    Gi1/0, Gi1/1, Gi1/2, Gi1/3` | |   | `101  PYTHON_VLAN_101                  active` | |   | `202  PYTHON_VLAN_202                  active` | |   | `303  PYTHON_VLAN_303                  active` | |   | `404  PYTHON_VLAN_404                  active` | |   | `505  PYTHON_VLAN_505                  active` | |   | `[...omitted for brevity]` | ## Telnet 实验 4:使用 while 循环配置随机 VLANs 与之前的实验一样,这次我们将通过 Telnet 在`lab-sw2`上创建相同的 VLANs 101、202、303、404、505。对于这个实验,您可以继续从`ubuntu20s1` Linux 服务器(192.168.183.132)工作,但是您还必须打开`lab-sw2` (192.168.183.102)的电源。复制`add_vlans_for_loop.py`文件并创建一个名为`add_vlans_while_loop.py`的新脚本。本实验结束时,`lab-sw2`应配置与`LAB-SW1`相同的一组 VLANs,如图 13-5 所示。 ![img/492721_1_En_13_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_13_Fig5_HTML.jpg) 图 13-5。 Telnet 实验 4,正在使用的设备 将具有以下 VLAN 描述的 VLANs 101、202、303、404 和 505 添加到`lab-sw2`。 * VLAN 101, `Data_vlan_101` * VLAN 202, `Data_vlan_202` * VLAN 303, `Voice_vlan_303` * VLAN 404, `Wireless_vlan_404` * VLAN 505, `Wireless_vlan_505` | # | 工作 | | --- | --- | | **1** | 假设你已经在`telnet_labs`目录下工作,使用`cp`命令复制`add_vlans_for_loop.py`并制作`add_vlans_while_loop.py`。按照命令创建新脚本,如下所示: | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ls` | |   | `add_lo_ospf1.py      add_vlans_single.py       add_vlans_for_loop.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$``cp      add_vlans_for_loop.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ls` | |   | `add_lo_ospf1.py  add       vlans_single.py  add_     vlans_for_loop.py       add_vlans_while_loop.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `nano add_vlans_while_loop.py` | | **2** | 现在修改您的新脚本,如下所示。该脚本使用一个`while`循环示例来添加与图 13.3 所示的`for`循环示例相同的 VLAN。同样,不要太在意您在这里使用的实际登录协议。试着理解这个示例脚本中使用的`while`循环的工作逻辑。有关更多信息,请参见嵌入式解释。 | |   | `GNU nano 4.8` `add_vlans_while_loop.py` | |   | `#!/usr/bin/env python3` | |   | `import getpass` | |   | `import telnetlib` | |   | `HOST =``"192.168.183.102"` | |   | `user = input("Enter your username: ")` | |   | `password = getpass.getpass()` | |   | `tn = telnetlib.Telnet(HOST)` | |   | `tn.read_until(b"Username: ")` | |   | `tn.write(user.encode('ascii') + b"\n")` | |   | `if password:` | |   | `tn.read_until(b"Password: ")` | |   | `tn.write(password.encode('ascii') + b"\n")` | |   | `# Get into to config mode` | |   | `tn.write(b"conf t\n")` | |   | `# Add 5 random vlans to vlans list and use  while loop to configure them to the switch` | |   | `vlans = [101, 202, 303, 404, 505]` `# vlans to add in list` | |   | `i = 0` `# initial index value` | |   | `while i < len(vlans):` `# while i is smaller than the length of vlans` | |   | `print(vlans[i])` `# print vlans item` | |   | `command_1 = "vlan " + str(vlans[i]) + "\n"` `# concatenate first command` | |   | `tn.write(command_1.encode('ascii'))` `# send command_1 with ASCII encoding` | |   | `command_2 = "name PYTHON_VLAN_" + str(vlans[i]) + "\n"` `# concatenate second command` | |   | `tn.write(command_2.encode('ascii'))` `# send command_2 with ASCII encoding` | |   | `i +=1` `# Same as i = i + 1` | |   | `tn.write(b"end\n")` | |   | `tn.write(b"exit\n")` | |   | `print("exiting")` | |   | `print(tn.read_all().decode('ascii'))` | |   | `^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` | | **3** | 完成`while`循环脚本后,检查与`lab-sw2` (192.168.183.102)的连接,并使用以下命令之一运行命令: | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `python add_vlans_while_loop.py` | |   | `OR` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `python3 add_vlans_while_loop.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ping 192.168.183.102 -c 3` | |   | `PING 192.168.183.102 (192.168.183.102) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.102: icmp_seq=1 ttl=255 time=41.1 ms` | |   | `64 bytes from 192.168.183.102: icmp_seq=2 ttl=255 time=27.3 ms` | |   | `64 bytes from 192.168.183.102: icmp_seq=3 ttl=255 time=22.5 ms` | |   | `--- 192.168.183.102 ping statistics ---` | |   | `3 packets transmitted, 3 received, 0% packet loss, time 2003ms` | |   | `rtt min/avg/max/mdev = 22.537/30.294/41.086/7.870 ms` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `python add_vlans_while_loop.py` | |   | `Enter your username:` `pynetauto` | |   | `Password:********` | |   | `101` | |   | `202` | |   | `303` | |   | `404` | |   | `505` | |   | `exiting` | |   | `**************************************************************************` | |   | `* IOSv is strictly limited to use for evaluation, demonstration and IOS  *` | |   | `* education. IOSv is provided as-is and is not supported by Cisco's      *` | |   | `* Technical Advisory Center. Any use or disclosure, in whole or in part, *` | |   | `* of the IOSv Software or Documentation to any third party for any       *` | |   | `* purposes is expressly prohibited except as otherwise authorized by     *` | |   | `* Cisco in writing.                                                      *` | |   | `**************************************************************************` | |   | `lab-sw2#conf t` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `lab-sw2(config)#vlan 101` | |   | `lab-sw2(config-vlan)#name PYTHON_VLAN_101` | |   | `lab-sw2(config-vlan)#vlan 202` | |   | `lab-sw2(config-vlan)#name PYTHON_VLAN_202` | |   | `lab-sw2(config-vlan)#vlan 303` | |   | `lab-sw2(config-vlan)#name PYTHON_VLAN_303` | |   | `lab-sw2(config-vlan)#vlan 404` | |   | `lab-sw2(config-vlan)#name PYTHON_VLAN_404` | |   | `lab-sw2(config-vlan)#vlan 505` | |   | `lab-sw2(config-vlan)#name PYTHON_VLAN_505` | |   | `lab-sw2(config-vlan)#end` | |   | `lab-sw2#exit` | | **4** | 从`lab-sw2`运行`show vlan`命令,检查新配置的 VLANs。如果您看到这里添加了 VLAN,那么您已经使用`while`环路成功地在交换机上配置了随机 VLAN。 | |   | `lab-sw2#` `show vlan` | |   | `VLAN Name                             Status    Ports` | |   | `---- -------------------------------- --------- -------------------------------` | |   | `1    default                          active    Gi0/0, Gi0/1, Gi0/2, Gi0/3` | |   | `Gi1/0, Gi1/1, Gi1/2, Gi1/3` | |   | `Gi2/0, Gi2/1, Gi2/2, Gi2/3` | |   | `Gi3/0, Gi3/1, Gi3/2, Gi3/3` | |   | `101  PYTHON_VLAN_101                  active` | |   | `202  PYTHON_VLAN_202                  active` | |   | `303  PYTHON_VLAN_303                  active` | |   | `404  PYTHON_VLAN_404                  active` | |   | `505  PYTHON_VLAN_505                  active` | |   | `[...omitted for brevity]` | ## Telnet 实验 5:使用 for ~ in range 环路方法配置 100 个 VLANs 在本实验中,您将在`LAB-SW1` (192.168.183.101)和`lab-sw2` (192.168.183.102)交换机上使用`for ~ in range`环路方法创建 100 个 VLANs。您将从同一个 Python 服务器,`ubuntu20s1` Linux 服务器(192.168.183.132)创建脚本。复制`add_vlans_while_loop.py`文件并创建一个名为`add_vlans_for_range.py`的新脚本。在本实验结束时,`LAB-SW1`和`lab-sw2`都应该配置有 VLANs 700 到 799。参见图 13-6 。 ![img/492721_1_En_13_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_13_Fig6_HTML.jpg) 图 13-6。 Telnet 实验 5,正在使用的设备 | # | 工作 | | --- | --- | | **1** | 使用 PuTTY SSH 到`ubuntu20s1` (192.168.183.132)来复制之前的 Telnet 脚本并创建`7.2.5_add_100_vlans.py`文件。 | |   | `pynetauto@ubuntu20s1:~$` `cd telnet_labs` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$ ls` | |   | `add_lo_ospf1.py      add_vlans_for_loop.py      add_vlans_single.py  add_vlans_while_loop.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$``cp add_vlans_while_loop.py add_100_vlans.py``pynetauto@ubuntu20s1:~/telnet_labs$` | | **2** | 这次您将使用一个脚本在两台交换机上配置 100 个 VLANs。您必须非常小心这个脚本的前导空格和代码块。与空白和脚本的代码块保持一致。修改脚本时,请参考嵌入的解释。使用`#`或`""" """`添加任意多的评论。在修改结束时,您的 Python 脚本应该类似于以下代码。 | |   | 该脚本可以下载,但是您应该尝试在 Linux 的 nano 文本编辑器上修改该脚本,以便熟悉 Linux 上的文本编辑。 | |   | `GNU nano 4.8` `add_100_vlans.py` | |   | `#!/usr/bin/env python3.8` | |   | `import getpass` | |   | `import telnetlib` | |   | `# HOSTS is a list with IP addresses of two switches` | |   | `HOSTS = ["192.168.183.101", "192.168.183.102"]` `# Create a list called HOSTS with two IPs` | |   | `user = input("Enter your username: ")` | |   | `password = getpass.getpass()` | |   | `# Use for loop to loop through the switch IPs` | |   | `# The rest of the script have been indented  to run under this block` | |   | `for HOST in HOSTS:` `# To loop through list, HOSTS` | |   | `print("SWITCH IP : " + HOST)` `# Marker to print out device IP` | |   | `tn = telnetlib.Telnet(HOST)` | |   | `tn.read_until(b"Username: ")` | |   | `tn.write(user.encode('ascii') + b"\n")` | |   | `if password:` | |   | `tn.read_until(b"Password: ")` | |   | `tn.write(password.encode('ascii') + b"\n")` | |   | `# Configure 100 VLANs with names using 'for ~ in range' loop` `# comment` | |   | `tn.write(b"conf t\n")` | |   | `# Use for n in range (starting vlan, ending vlan, (optional-stepping not used))` `# comment` | |   | `for n in range (700, 800):` `# vlan range to add, 700 – 799, the last number is not counted` | |   | `tn.write(b"vlan " + str(n).encode('UTF-8') + b"\n")` `# Now part of vlan for loop` | |   | `tn.write(b"name PYTHON_VLAN_" + str(n).encode('UTF-8') + b"\n")` `# Now part of vlan for loop` | |   | `tn.write(b"end\n")` | |   | `tn.write(b"exit\n")` | |   | `print(tn.read_all().decode('ascii'))` | |   | `^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` | | **3** | 您的 Python 服务器需要从 Ubuntu Python 服务器到两个交换机的良好网络连接;检查开关的通信。仅当您的服务器可以访问这两个 IP 地址时,才运行脚本。如果您有任何通信问题,您必须先解决问题,然后才能继续下一步。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ping 192.168.183.101 -c 3` | |   | `PING 192.168.183.101 (192.168.183.101) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.101: icmp_seq=1 ttl=255 time=5.15 ms` | |   | `64 bytes from 192.168.183.101: icmp_seq=2 ttl=255 time=5.02 ms` | |   | `64 bytes from 192.168.183.101: icmp_seq=3 ttl=255 time=5.42 ms` | |   | `--- 192.168.183.101 ping statistics ---` | |   | `4 packets transmitted, 4 received, 0% packet loss, time 3005ms` | |   | `rtt min/avg/max/mdev = 5.019/5.208/5.419/0.145 ms` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ping 192.168.183.102 -c 3` | |   | `PING 192.168.183.102 (192.168.183.102) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.102: icmp_seq=1 ttl=255 time=9.91 ms` | |   | `64 bytes from 192.168.183.102: icmp_seq=2 ttl=255 time=9.60 ms` | |   | `64 bytes from 192.168.183.102: icmp_seq=3 ttl=255 time=9.55 ms` | |   | `--- 192.168.183.102 ping statistics ---` | |   | `4 packets transmitted, 4 received, 0% packet loss, time 3006ms` | |   | `rtt min/avg/max/mdev = 9.549/9.726/9.909/0.154 ms` | | **4** | 网络连接似乎没问题。现在,让我们运行`add_100_vlans.py`脚本,在两台交换机上添加 100 个 VLANs。该脚本将远程登录到第一台交换机以添加 100 个 VLAN,然后远程登录到第二台交换机以添加另外 100 个 VLAN。这个脚本可能需要几分钟才能完成,请耐心等待。此外,如果你的实验室配置是在像我这样的旧电脑上,考虑将`range`的数量从 100 减少到 10,这样你就可以加快这个过程。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `python add_100_vlans.py` | |   | `Enter your username:` `pynetauto` | |   | `Password:*******` | |   | `SWITCH IP : 192.168.183.101` | |   | `**************************************************************************` | |   | `* IOSv is strictly limited to use for evaluation, demonstration and IOS  *` | |   | `* education. IOSv is provided as-is and is not supported by Cisco's      *` | |   | `* Technical Advisory Center. Any use or disclosure, in whole or in part, *` | |   | `* of the IOSv Software or Documentation to any third party for any       *` | |   | `* purposes is expressly prohibited except as otherwise authorized by     *` | |   | `* Cisco in writing.                                                      *` | |   | `**************************************************************************` | |   | `LAB-SW1#conf t` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `LAB-SW1(config)#vlan 700` | |   | `LAB-SW1(config-vlan)#name PYTHON_VLAN_700` | |   | `LAB-SW1(config-vlan)#vlan 701` | |   | `[...omitted for brevity]` | |   | `LAB-SW1(config-vlan)#vlan 799` | |   | `LAB-SW1(config-vlan)#name PYTHON_VLAN_799` | |   | `LAB-SW1(config-vlan)#end` | |   | `LAB-SW1#exit` | |   | `lab-sw2#` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `[...omitted for brevity]` | | **5** | 给脚本一点时间来完成其任务,并使用`show vlan`检查两个交换机的配置更改。您应该会在两台交换机上看到新配置的 VLAN 700 到 799。 | |   | `Config message and check added vlans on LAB-SW1` | |   | `LAB-SW1#` | |   | `*Jan 11 21:38:00.558: %SYS-5-CONFIG_I: Configured from console by pynetauto on vty0 (192.168.183.132)` | |   | `LAB-SW1#` `show vlan` | |   | `[...omitted for brevity]` | |   | `700  PYTHON_VLAN_700                  active` | |   | `701  PYTHON_VLAN_701                  active` | |   | `...` | |   | `798  PYTHON_VLAN_798                  active` | |   | `799  PYTHON_VLAN_799                  active` | |   | `[...omitted for brevity]` | |   | `Config message and check added vlans on lab-sw2` | |   | `lab-sw2#` | |   | `*Jan 11 21:47:09.432: %SYS-5-CONFIG_I: Configured from console by pynetauto on vty0 (192.168.183.132)` | |   | `lab-sw2#` `show vlan` | |   | `[...omitted for brevity]` | |   | `700  PYTHON_VLAN_700                  active` | |   | `701  PYTHON_VLAN_701                  active` | |   | `...` | |   | `798  PYTHON_VLAN_798                  active` | |   | `799  PYTHON_VLAN_799                  active` | |   | `[...omitted for brevity]` | | **6** | (可选任务)要从交换机中删除(反转)100 个 VLANs,您只需修改原始脚本中的两行代码。以下示例将复制原始脚本,并将其重命名为`reverse_100_vlans.py`。在运行脚本之前,打开这个新文件并修改两行代码,如下所示。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `cp add_100_vlans.py  reverse_100_vlans.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `nano reverse_100_vlans.py` | |   | `Original code to update:...` | |   | `for n in range (700, 800):` | |   | `tn.write(b"vlan " + str(n).encode('UTF-8') + b"\n")` | |   | `tn.write(b"name PYTHON_VLAN_" + str(n).encode('UTF-8') + b"\n")` | |   | 更新的反向代码: | |   | `...` | |   | `for n in range (700, 800):` | |   | `tn.write(b"``no` | |   | `#` `tn.write(b"name PYTHON_VLAN_" + str(n).encode('UTF-8') + b"\n")` | |   | 在您的服务器上,运行`python reverse_100_vlans.py`来完全删除 vlans。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `python reverse_100_vlans.py` | |   | `Enter your username:` `pynetauto` | |   | `Password: ********` | * 使用以下 VLAN 描述配置 VLAN 700–799: * VLAN 700–799(即`PYTHON_VLAN_700`到`PYTHON_VLAN_799`) ## Telnet 实验 6:使用外部文件中的 IP 地址在多台设备上添加权限为 3 的用户 在本实验中,您将为新用户配置网络中所有路由器和交换机的有限权限,因此您必须打开图 13-7 所示设备的电源。您必须创建一个包含 IP 地址的单独文件,以便您的脚本可以从该文件中读取 IP 地址,并按顺序配置每个设备。 ![img/492721_1_En_13_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_13_Fig7_HTML.jpg) 图 13-7。 Telnet 实验 6,正在使用的设备 新用户应该被授予初级网络管理员权限来运行`show`命令。您将为该新用户分配权限 3,并允许该用户查看网络设备的运行配置和接口状态。此外,将文件模式更改为可执行文件来运行您的脚本,而无需键入`python`或`python3`。 在本实验结束时,您将在每台设备上创建一个本地帐户,以便新用户可以运行`show running-config view full`、`show ip interface brief`和其他`show`命令。 | # | 工作 | | --- | --- | | **1** | 在`ubuntu20s1` Python 服务器上,创建一个包含所有网络设备 IP 地址的文本文件。用换行符分隔 IP 地址,这样每行包含每台设备的单个 IP 地址。要创建并保存该文件,请遵循以下说明: | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `touch ip_addresses.txt` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `nano ip_addresses.txt` | |   | GNU nano 4.8 `ip_addresses.txt` | |   | `192.168.183.10` | |   | `192.168.183.20` | |   | `192.168.183.101` | |   | `192.168.183.102` | |   | `192.168.183.133` | |   | `^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` | |   | *注意:`ip_addresses.txt`文件必须与`add_junioradmin.py`文件在同一个目录中。 | | **2** | 与前面的步骤一样,让我们重用旧脚本来创建一个新脚本。复制实验 5 中的脚本,并创建一个名为`add_junioradmin.py`的新脚本。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `cp add_100_vlans.py add_junioradmin.py` | |   | `GNU nano 4.8` `add_junioradmin.py` | |   | `#!/usr/bin/env python3` | |   | `import getpass` | |   | `import telnetlib` | |   | `import time  # imports time module` | |   | `user = input("Enter your username: ")` | |   | `password = getpass.getpass()` | |   | `# Open ip_addresses.txt to read IP addresses` | |   | `file = open("ip_addresses.txt")` `# Open and read an external file` | |   | `for ip in file:` `# loop through read information` | |   | `print("Now configuring : " + ip)` `# Task beginning statement` | |   | `HOST = (ip.strip())` `# Strips any white spaces` | |   | `tn = telnetlib.Telnet(HOST)` `# Use read IP to log into a single device` | |   | `tn.read_until(b"Username: ")` | |   | `tn.write(user.encode('ascii') + b"\n")` | |   | `if password:` | |   | `tn.read_until(b"Password: ")` | |   | `tn.write(password.encode('ascii') + b"\n")` | |   | `time.sleep(1)` `# Adds 1 second pause for device to respond` | |   | `# Configure a new user with privilege 3, allow show running-config` `# comment` | |   | `tn.write(b"conf t\n") # Enter configuration mode` | |   | `tn.write(b"username junioradmin privilege 3 password cisco321\n")` `# Configure new pri 3 user` | |   | `tn.write(b"privilege exec all level 3 show running-config\n")` `# Allow show running-config command` | |   | `print("Added a new privilege 3 user")` `# Task ending statement` | |   | `tn.write(b"end\n")` | |   | `tn.write(b"exit\n")` | |   | `print(tn.read_all().decode('ascii'))` | |   | `^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` | | **3** | 在运行脚本之前,请确认 IP 地址和脚本文件都在 Linux 服务器上的同一个目录中。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$ ls` | |   | `add_lo_ospf1.py        add_vlans_while_loop.py   ip_addresses.txt     add_vlans_single.py    add_100_vlans.py      add_vlans_for_loop.py       add_junioradmin.py` | | **4** | 从`ubuntu20s1`服务器,检查网络中所有网络设备的连接。我们可以在 Linux 上使用`fping`通过一个命令行安装和 ping 多个设备。如图所示进行安装,并 ping 我们拓扑中的所有五台设备。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `sudo apt install fping` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `fping 192.168.183.10 192.168.183.20 192.168.183.101 192.168.183.102 192.168.183.133` | |   | `192.168.183.10 is alive` | |   | `192.168.183.133 is alive` | |   | `192.168.183.20 is alive` | |   | `192.168.183.102 is alive` | |   | `192.168.183.101 is alive` | |   | 要了解如何使用`fping`,请访问以下网址。 | |   | URL: [`https://www.2daygeek.com/how-to-use-ping-fping-gping-in-linux/`](https://www.2daygeek.com/how-to-use-ping-fping-gping-in-linux/) | | **5** | 我们已经在开头添加了`#!/usr/bin/env python3.8`,正如所解释的,使用这一行是为了让 Linux 系统能够识别这个脚本或应用需要从 Python 3 运行。要使用这一行,我们首先必须使我们的脚本可执行。如果您列出我们刚刚创建并准备运行的文件,您会看到文件中缺少了`x`(可执行文件)选项。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ls -l add_junior*` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 1298 Jan 12 01:16 add_junioradmin.py` | |   | 为了能够在不使用`python`命令的情况下运行该脚本,我们可以使用`chmod +x`命令更改该文件的模式,如下所示: | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `chmod +x ./ add_junioradmin.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$ ls -l add_junior*` | |   | `-rwxrwxr-x 1 pynetauto pynetauto 1298 Jan 12 01:16 add_junioradmin.py` | |   | 此时,您可以使用`./add_junioradmin.py`命令运行您的 Python 脚本。 | |   | 如果您想更进一步,并且想只使用脚本文件名运行脚本,那么您可以将下面的`PATH`变量添加到您的 Linux 服务器中。当您登录到会话中时,此命令只是临时的或会话性的。有一些方法可以永久地设置它,但这不在本书讨论范围之内。在此阶段,您可以仅使用脚本名称运行 Python 脚本。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `PATH="$(pwd):$PATH"` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `add_junioradmin.py` | |   | `Enter your username: ^Z` | |   | `[2]+  Stopped                 add_junioradmin.py` | |   | 如果希望以后运行该脚本,请按 Ctrl+Z 退出;如果没有,继续运行步骤 6 中的脚本。 | | **6** | 让我们使用文件名运行脚本。该脚本会将初级管理员添加到所有五台设备中,如下所示: | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `add_junioradmin.py` | |   | `Enter your username:` `pynetauto` | |   | `Password:********` | |   | `Now configuring : 192.168.183.10` | |   | `Added a new privilege 3 user` | |   | `**************************************************************************` | |   | `* IOSv is strictly limited to use for evaluation, demonstration and IOS  *` | |   | `* education. IOSv is provided as-is and is not supported by Cisco's      *` | |   | `* Technical Advisory Center. Any use or disclosure, in whole or in part, *` | |   | `* of the IOSv Software or Documentation to any third party for any       *` | |   | `* purposes is expressly prohibited except as otherwise authorized by     *` | |   | `* Cisco in writing.                                                      *` | |   | `**************************************************************************` | |   | `LAB-R1#conf t` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `LAB-R1(config)#username junioradmin privilege 3 password cisco321` | |   | `LAB-R1(config)#privilege exec all level 3 show running-config` | |   | `LAB-R1(config)#file privilege 3` | |   | `LAB-R1(config)#end` | |   | `LAB-R1#exit` | |   | `Now configuring : 192.168.183.20` | |   | `Added a new privilege 3 user` | |   | `[...omitted for brevity]` | |   | `lab-sw2#conf t` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `lab-sw2(config)#username junioradmin privilege 3 password cisco321` | |   | `lab-sw2(config)#privilege exec all level 3 show running-config` | |   | `lab-sw2(config)#end` | |   | `lab-sw2#exit` | |   | `Now configuring : 192.168.183.133` | |   | `Added a new privilege 3 user` | |   | `R1#conf t` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `R1(config)#username junioradmin privilege 3 password cisco321` | |   | `R1(config)#privilege exec all level 3 show running-config` | |   | `R1(config)#end` | |   | `R1#exit` | | **7** | 登录每个设备,检查用户配置以及`privilege exec level 3`命令。 | |   | `LAB-SW1#` `show run | in username junioradmin` | |   | `username junioradmin privilege 3 password 0 cisco321` | |   | `LAB-SW1#` `show run | in level 3` | |   | `privilege exec all level 3 show running-config` | |   | `privilege exec level 3 show` | | **8** | 现在从你的 Windows 主机 PC 使用 PuTTY 以`junioradmin`用户和密码`cisco321`登录并运行`show ip interface brief`或`show running-config view full`命令。如果您想从 Ubuntu 服务器登录设备,您可以使用 Telnet 192.168.183.X,其中 X 是您想登录的设备的最后一个八位字节。 | |   | 以下示例显示了从`LAB-SW1`交换机和`lab-r2`路由器运行的登录和命令。 | |   | 从您的主机上,使用 PuTTY 通过 Telnet 登录到`LAB-SW1` (192.168.183.101)并使用密码`cisco321`作为`junioradmin`用户登录。 | |   | `LAB-SW1#` | |   | `[...omitted for brevity]` | |   | `User Access Verification` | |   | `Username:` `junioradmin` | |   | `Password:cisco321` | |   | `[...omitted for brevity]` | |   | `LAB-SW1#` `show ip interface brief` | |   | `Interface              IP-Address      OK? Method Status                Protocol` | |   | `GigabitEthernet0/0     unassigned      YES unset  up                    up` | |   | `GigabitEthernet0/1     unassigned      YES unset  up                    up` | |   | `GigabitEthernet0/2     unassigned      YES unset  up                    up` | |   | `GigabitEthernet0/3     unassigned      YES unset  down                  down` | |   | `[...omitted for brevity]` | |   | 从您的主机上,使用 PuTTY 通过 Telnet 登录到`lab-r2` (192.168.183.20)并使用密码`cisco321`作为`junioradmin`用户登录。 | |   | `lab-r2#` | |   | `[...omitted for brevity]` | |   | `User Access Verification` | |   | `Username:` `junioradmin` | |   | `Password:cisco321` | |   | `[...omitted for brevity]` | |   | `lab-r2#` `show running-config view full` | |   | `Building configuration...` | |   | `Current configuration : 3625 bytes` | |   | `!` | |   | `! Last configuration change at 23:21:58 UTC Mon Jan 11 2021` | |   | `!` | |   | `version 15.6` | |   | `service timestamps debug datetime msec` | |   | `[...omitted for brevity]` | * *新用户名* : `junioradmin` * *密码* : `cisco321` * *特权等级* : `3` * *命令 1* : `privilege exec all level 3 show running-config` * *命令 2* : `file privilege 3` 您已经使用从外部文件读取的 IP 地址在多台设备上成功创建了初级管理员用户帐户。请注意,IP 地址不必是连续的;如果使用列表或外部文件,它们可以是随机的 IP 地址。您从这些 Telnet 实验中学到的东西也可以在 SSH 实验中使用,只需稍加修改。 ## Telnet 实验 7:将运行配置(或启动配置)备份到本地服务器存储 在 Telnet 实验 7 中,您将复制并修改之前的 Telnet 脚本,以捕获网络中每台设备的当前运行配置。备份的`running-config`文件将保存在本地驱动器上。在本实验中,您将使用`datetime`模块获取时间戳,并添加带有该时间戳的文件名,以便了解备份的时间。您需要打开所有路由器和交换机的电源,如实验 6 所示。见图 13-8 。 ![img/492721_1_En_13_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_13_Fig8_HTML.jpg) 图 13-8。 Telnet 实验 7,正在使用的设备 在本实验结束时,您将在 Python 服务器的本地存储上备份每台设备的运行配置和时间戳。 | # | 工作 | | --- | --- | | **1** | 同样,让我们从复制上一个实验的脚本开始实验。这里给出的文件名是`take_backups.py`,但是你不必遵循这个命名约定;请为该文件取一个更有意义的名称,以便于记忆。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `cp add_junior_user.py take_backups.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `nano take_backups.py` | | **2** | 现在,在 nano(或 vi)文本编辑器中打开该文件,并在以下源代码中进行突出显示的修改: | |   | `GNU nano 4.8` `take_backups.py` | |   | `#!/usr/bin/env python3` | |   | `import getpass` | |   | `import telnetlib` | |   | `from datetime import datetime` `# Import datetime module from datetime library` | |   | `saved_time = datetime.now().strftime("%Y%m%d_%H%M%S")` `# Change the \` | |   | `# format of current time into a string` | |   | `user = input("Enter your username: ") # Ask for username and password` | |   | `password = getpass.getpass()` | |   | `file = open("ip_addresses.txt") # Open ip_addresses.txt to read IP addresses` | |   | `# Telnets into Devices & runs show running-config and \` | |   | `# save it to a file with a timestamp` | |   | `for ip in file:` | |   | `print ("``Getting running-config from` | |   | `HOST = ip.strip()` | |   | `tn = telnetlib.Telnet(HOST)` | |   | `tn.read_until(b"Username: ")` | |   | `tn.write(user.encode('ascii') + b"\n")` | |   | `if password:` | |   | `tn.read_until(b"Password: ")` | |   | `tn.write(password.encode('ascii') + b"\n")` | |   | `#Makes Term length to 0, run shows commands & reads all output,` `\` | |   | `# then saves files with time stamp` `# Comment` | |   | `tn.write(("terminal length 0\n").encode('ascii'))` `# Change terminal length to 0` | |   | `tn.write(("show clock\n").encode('ascii'))` `# Disply time` | |   | `tn.write(("show running-config\n").encode('ascii'))` `# Show running-configuration` | |   | `tn.write(("exit\n").encode('ascii'))` `# Exit session` | |   | `readoutput = tn.read_all()` `# Read output` | |   | `saveoutput = open(str(saved_time) + "_running_config_" + HOST, "wb")` `# saved_time is the time the file was saved, HOST is the IP address of the device.` | |   | `saveoutput.write(readoutput) # Write the output to the file` | |   | `saveoutput.close` `# Save and close the file` | |   | `^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` | | **3** | 再次使用 Linux `fping`命令来检查您网络上的网络连接。如果有任何节点(网络设备)不可访问,您必须首先解决连接问题。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `sudo apt install fping` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `fping 192.168.183.10 192.168.183.20 192.168.183.101 192.168.183.102 192.168.183.133` | |   | `192.168.183.10 is alive` | |   | `192.168.183.133 is alive` | |   | `192.168.183.20 is alive` | |   | `192.168.183.102 is alive` | |   | `192.168.183.101 is alive` | | **4** | 现在,从您的`ubuntu20s1`服务器使用`./take_backup.py`命令运行脚本。如果您从上一个实验中复制了脚本,那么文件属性应该被保留,并且您的新脚本应该已经是一个可执行文件了。是时候在本章中最后一次运行代码了。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ls -l take*` | |   | `-rwxrwxr-x 1 pynetauto pynetauto 1574 Jan 12 10:50 take_backups.py` | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `./take_backups.py` | |   | `Enter your username:` `pynetauto` | |   | `Password:` `********` | |   | `Getting running-config from 192.168.183.10` | |   | `Getting running-config from 192.168.183.20` | |   | `Getting running-config from 192.168.183.101` | |   | `Getting running-config from 192.168.183.102` | |   | `Getting running-config from 192.168.183.133` | | **4** | 在脚本成功运行之后,您可以运行`ls –lh` Linux 命令来检查您的设备的本地目录的备份`running-config`文件。文件应该以年、月、日开头,后面是备份的时间。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `ls -lh 2021*` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 4.3K Jan 12 10:53 20210112_105318_running_config_192.168.183.10` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 5.4K Jan 12 10:53 20210112_105318_running_config_192.168.183.101` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 4.8K Jan 12 10:54 20210112_105318_running_config_192.168.183.102` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 1.7K Jan 12 10:54 20210112_105318_running_config_192.168.183.133` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 4.5K Jan 12 10:53 20210112_105318_running_config_192.168.183.20` | | **5** | 使用`cat`命令检查是否捕捉到了正确的信息。 | |   | `pynetauto@ubuntu20s1:~/telnet_labs$` `cat 20210112_105318_running_config_192.168.183.10` | |   | `**************************************************************************` | |   | `* IOSv is strictly limited to use for evaluation, demonstration and IOS  *` | |   | `* education. IOSv is provided as-is and is not supported by Cisco's      *` | |   | `* Technical Advisory Center. Any use or disclosure, in whole or in part, *` | |   | `* of the IOSv Software or Documentation to any third party for any       *` | |   | `* purposes is expressly prohibited except as otherwise authorized by     *` | |   | `* Cisco in writing.                                                      *` | |   | `**************************************************************************` | |   | `LAB-R1#terminal length 0` | |   | `LAB-R1#show clock` | |   | `*08:01:50.307 UTC Tue Jan 12 2021` | |   | `LAB-R1#show running-config` | |   | `Building configuration...``Current configuration : 3418 bytes` | |   | `!` | |   | `version 15.6` | |   | `service timestamps debug datetime msec` | |   | `service timestamps log datetime msec` | |   | `no service password-encryption` | |   | `!` | |   | `hostname LAB-R1` | |   | `!` | |   | `boot-start-marker` | |   | `boot-end-marker` | |   | `!` | |   | `!` | |   | `enable password cisco123` | |   | `!` | |   | `no aaa new-model` | |   | `[...omitted for brevity]` | 您已经登录到每台设备,并成功地将路由器和交换机的当前运行配置备份到本地目录。将正在运行的配置备份到 S/FTP 服务器甚至更容易,但本实验是为 SSH 实验保留的。此时,尝试修改您的脚本并运行`write memory`或`copy running-config startup-config`来保存所有的配置更改。接下来,您将尝试使用`paramiko`和`netmiko`库做一些 SSH 实验。 ## 摘要 在本章中,我们重点介绍了七个简单的 Telnet 实验,以利用我们在前面章节中获得的知识。您已经学会了如何使用`fping`和`datetime`模块。您还首先交互地执行了简单的配置更改,然后通过使用 Python 脚本,利用循环和`range`命令的力量进行了更改。此外,您还学习了从外部文件读取 IP 地址,并在脚本中使用这些信息。在最后的实验中,您使用 Python `telnetlib`对所有五台网络设备进行了配置备份,每个文件名都带有时间戳。在第十四章中,您将继续使用`paramiko`和`netmiko` SSH 库通过 SSH 连接探索 Python 网络自动化。 # 十四、Python 网络自动化实验室:SSH `paramiko`和`netmiko` 在本章中,您将使用 Python 的 SSH 库`paramiko`和`netmiko`来控制您的网络设备。`paramiko`是 Ansible 对网络设备的 SSH 连接管理所依赖的,而`netmiko`是`paramiko`的工程师友好版本,因为`netmiko`也依赖于`paramiko`。通过研究这些网络模块如何工作,您可以了解依赖这些网络模块的其他应用的内部工作方式。在本章的前半部分,您将学习使用 Python 脚本和`paramiko`库来替换基本的网络工程师手工任务。在本章的后半部分,你将学习使用`netmiko`库编写 Python 脚本。一旦您掌握了如何使用这些 SSH 模块,您就可以立即将它们应用到您的工作中。这些 SSH 实验将作为本书末尾开发 IOS XE 升级应用的基础。 ![img/492721_1_En_14_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Figa_HTML.jpg) ## 使用 paramiko 和 netmiko 库的 Python 网络自动化实验室 在前一章中,我们使用 Telnet 实验室重点介绍了基本的 Python 网络概念。无论您是使用 Telnet 还是 SSH,都可以应用相同的 Python 网络概念。当然,SSH 是比 Telnet 更安全的远程登录协议,它也使用不同的库来登录网络设备。如您所知,Telnet 是一种纯文本网络协议,与 SSH 相比不安全。在大多数安全网络中,通常禁止使用 Telnet,只允许加密的 SSH 连接。要使用 Python 连接到使用 SSH 的 Cisco 设备,必须首先安装 Python `paramiko`库,并且必须在 Cisco 设备上配置加密的 RSA 密钥。为了开始这一章,让我们首先安装`paramiko`库来开始我们的 SSH 实验。 ## Python SSH 实验室:paramiko 在您的 Ubuntu Python automation 服务器上,您可以使用下面显示的`pip`命令安装`paramiko`: ```py pynetauto@ubuntu20s1:~$ pip3 install paramiko ``` 完成`paramiko`安装后,快速运行 Python 解释器会话,并运行`import paramiko`来检查库是否可以正确导入。准备好之后,开始第一个 SSH 实验。 ```py pynetauto@ubuntu20s1:~$ python 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 paramiko >>> ``` ## paramiko 实验 1:在 Python 解释器中交互配置所有设备的时钟和时区 在本实验中,您将登录到`ubunsu20s1` Python 自动化服务器上的 Python 解释器,并编写一个实时脚本来与所有路由器和交换机交互,以更改它们的时间和时区设置。您必须打开所有三台路由器和两台交换机的电源。如果您的 PC 使用较旧的低性能 CPU 或更少的内存,您只需打开一台路由器和一台交换机的电源;这将使您在本实验中避免高 CPU 利用率和内存争用问题。见图 14-1 。 ![img/492721_1_En_14_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Fig1_HTML.jpg) 图 14-1。 SSH paramiko 实验室 1,正在使用的设备 您将练习一个交互式 SSH 会话,以便从 Python 解释器 shell 管理您的网络设备。在通过 SSH 连接到 Cisco 设备之前,我们必须首先为每台设备生成 RSA 密钥。由于我们在实验室中没有使用 AAA RADIUS 服务器进行身份认证,因此我们将为每台设备创建本地证书。按照以下任务为第一个 SSH 实验做准备: | # | 工作 | | --- | --- | | **1** | 使用以下 Cisco IOS 命令在每台设备上创建 1,024 位本地 RSA 密钥,并仅启用 SSH 连接来管理每台设备。以下示例仅显示了`LAB-R1`路由器上的配置,但相同的配置必须应用于所有其它设备,以便在您的网络上完全禁用 Telnet。`LAB-R1#` `configure terminal` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `LAB-R1(config)#` `ip domain-name pynetauto.local` | |   | `LAB-R1(config)#` `crypto key generate rsa` | |   | `The name for the keys will be: LAB-R1.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 2 seconds)` | |   | `LAB-R1(config)#` | |   | `*Aug  9 13:04:41.381: %SSH-5-ENABLED: SSH 1.99 has been enabled` | |   | `LAB-R1(config)#` `line vty 0 15` | |   | `LAB-R1(config-line)#` `transport input all` | |   | `LAB-R1(config-line)#` `end` | |   | `LAB-R1#` `write memory` | |   | 现在在`lab-r2`、`LAB-SW1`、`lab-sw2`和`R1`上应用先前的配置。您可以一次复制以下命令,并将其粘贴到每个设备的控制台中: | |   | `configure terminal` | |   | `ip domain-name pynetauto.local` | |   | `crypto key generate rsa` | |   | `1024` | |   | `line vty 0 15` | |   | `transport input all` | |   | `do write memory` | | **2** | 您可以使用`ssh –l user_name IP_address`命令 SSH 到任意两台 Cisco 设备之间的另一台设备。从`R1`中,使用以下命令 SSH 到所有其他路由器和交换机: | |   | `R1#` `ssh -l pynetauto 192.168.183.10` | |   | `**************************************************************************` | |   | `* IOSv is strictly limited to use for evaluation, demonstration and IOS  *` | |   | `* education. IOSv is provided as-is and is not supported by Cisco's      *` | |   | `* Technical Advisory Center. Any use or disclosure, in whole or in part, *` | |   | `* of the IOSv Software or Documentation to any third party for any       *` | |   | `* purposes is expressly prohibited except as otherwise authorized by     *` | |   | `* Cisco in writing.                                                      *` | |   | `**************************************************************************` | |   | `Password:*******` | |   | `[...omitted for brevity]` | |   | `LAB-R1#` `exit` | |   | `[Connection to 192.168.183.10 closed by foreign host]` | |   | `R1#` | |   | 尝试 ssh 到其他设备和 vise vesa。 | |   | `R1#` `ssh -l pynetauto 192.168.183.20` | |   | `R1#` `ssh -l pynetauto 192.168.183.101` | |   | `R1#` `ssh -l pynetauto 192.168.183.102` | | **3** | 如果想从 Linux 服务器测试 SSH 连接,可以使用`ssh user_name IP_address`命令。但是,由于遗留的 SSH 密钥交换问题,您可能必须指定加密密钥类型,以便从 Linux Python 服务器 SSH 到 Cisco 设备。 | |   | Ubuntu SSH 登录到 R1 实验室(192.168.183.10)示例: | |   | `pynetauto@ubuntu20s1:~$` `ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -c 3des-cbc pynetauto@192.168.183.10` | |   | `The authenticity of host '192.168.183.10 (192.168.183.10)' can't be established.` | |   | `RSA key fingerprint is SHA256:D9HqPuccjTD+WAGLMOYkyZ3KooHqYqe+5n7Hs2AI67I.` | |   | `Are you sure you want to continue connecting (yes/no/[fingerprint])?` `yes` | |   | `Warning: Permanently added '192.168.183.10' (RSA) to the list of known hosts` `.` | |   | `[...omitted for brevity]` | |   | `Password: *********` | |   | `[...omitted for brevity]` | |   | `LAB-R1#` `exit` | |   | `Connection to 192.168.183.10 closed.` | |   | `pynetauto@ubuntu20s1:~$` | |   | `Ubuntu SSH login to LAB-SW1 (192.168.183.101) example:` | |   | `pynetauto@ubuntu20s1:~$` `ssh -l pynetauto 192.168.183.101 -c aes256-cbc -oKexAlgorithms=+diffie-hellman-group1-sha1` | |   | `The authenticity of host '192.168.183.101 (192.168.183.101)' can't be established.` | |   | `RSA key fingerprint is SHA256:0sbazxGQ82A3KAC26l6msWR3BrklVXnW0sHChkij23g.` | |   | `Are you sure you want to continue connecting (yes/no/[fingerprint])?` `yes` | |   | `Warning: Permanently added '192.168.183.101' (RSA) to the list of known hosts.` | |   | `[...omitted for brevity]` | |   | `Password: *********` | |   | `[...omitted for brevity]` | |   | `LAB-SW1#` `exit` | |   | `Connection to 192.168.183.101 closed.` | |   | `pynetauto@ubuntu20s1:~$` | | **4** | 假设您想更进一步,简化从 Linux 服务器的 SSH 登录。在这种情况下,首先必须检查 Linux 服务器上使用的 SSH 版本,以避免 Linux 服务器和 Cisco 设备之间可能出现的 Diffie-Hellman 密钥交换问题。通过修改`ubuntu20s1`上的`.ssh/config`文件,在您的 Linux 服务器上永久使用`diffie-hellman-group1-sha1`密钥交换方法。 | |   | 要检查 Python 服务器上的 SSH 版本,请使用以下命令: | |   | `pynetauto@ubuntu20s1:~$` `ssh -V` | |   | `OpenSSH_8.2p1 Ubuntu-4, OpenSSL 1.1.1f  31 Mar 2020` | |   | 要对密钥交换方法进行硬编码,请打开`/home/user_name/.ssh/config`文件,并将以下信息添加到文件中。这将在 SSH 客户端(`ubuntu20s1`)上启用旧的算法,允许它连接到旧类型的 SSH 服务器(在本例中是 Cisco 设备)。 | |   | `pynetauto@ubuntu20s1:~$` `nano /home/pynetauto/.ssh/config` | |   | `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.133` | |   | `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` | ![img/492721_1_En_14_Figb_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Figb_HTML.gif) 此外,如果您由于这个问题遇到了已知主机问题,您可以按照下面论坛中的说明从您的 Linux 服务器中删除记录,并尝试重新建立到 Cisco 网络设备的 SSH 连接。 [`https://superuser.com/questions/30087/remove-key-from-known-hosts`](https://superuser.com/questions/30087/remove-key-from-known-hosts) 此外,如果您在 Cisco 路由器或交换机上遇到 RSA 密钥问题,您可以“归零”RSA 以重新创建 RSA 密钥。您可以使用以下命令清除上一个密钥: `R1 (config)#` `crypto key zeroize rsa` `Configure SSH Configuration again` `R1(config)#` `hostname ` `R1(config)#` `ip domain-name ` `R1(config)#` `crypto key generate rsa` `R1(config)#` `ip ssh version 2` 如果 GNS3 中的设备遇到 Diffie-Hellman 密钥交换问题,并且您无法找到解决方案,则保存运行配置,从拓扑中删除节点,然后重新配置您的设备;这可能与 GNS3、VMware 和 Cisco 映像文件不兼容问题有关。 | # | 工作 | | --- | --- | | **5** | 现在,让我们在`ubuntu20s1`服务器上打开 Python 解释器,输入以下内容,通过 SSH 实时连接配置正确的时间和时区。请将时区更新为您自己的当地时间。是的,你需要在交互式会话中逐字键入以下内容来练习编写代码。Python 开发工程师每天都要编写成百上千行代码,这可不是在公园里散步。 | |   | 同样,如果您使用不同的 IP 地址范围,请更新 IP 地址。此外,每个冒号后的四个前导空格要精确,并注意任何特殊方法中的大写。运行 Python 解释器会话的命令时,请确保将时间更改为当前时间和时区。 | |   | `>>>``import paramiko` | |   | `>>>``import time` | |   | `>>>` | |   | `>>>``ip_addresses = ["192.168.183.10", "192.168.183.20", "192.168.183.101", "192.168.183.102", "192.168.183.133"]` | |   | `>>>` | |   | > > >`username = "pynetauto"` `# username variable` | |   | `>>>``password = "cisco123"` | |   | `>>>` | |   | `>>>``ssh_client = paramiko.SSHClient()` | |   | `>>>``ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())` | |   | `>>>``for ip in ip_addresses:` | |   | `...``ssh_client.connect(hostname=ip, username=username, password=password)` | |   | `...``print("Connected to " + ip + "\n")` | |   | `...``remote_connection = ssh_client.invoke_shell()` | |   | `...` `remote_connection.send("terminal length 0\n")` | |   | `...``remote_connection.send("configure terminal\n")` | |   | `...``remote_connection.send("clock timezone AEST +10\n")` | |   | `...``remote_connection.send("clock summer-time AEST recurring\n")` | |   | `...``remote_connection.send("exit\n")` | |   | `...``time.sleep(1)` | |   | `...``remote_connection.send("clock set 15:15:00 12 Jan 2021\n")` | |   | `...``remote_connection.send("copy running-config startup-config\n")` | |   | `...``remote_connection.send("end\n")` | |   | `...``output = remote_connection.recv(6000)` | |   | `...``print((output).decode('ascii'))` | |   | `...``time.sleep(2)` | |   | `...``ssh_client.close()` | | **6** | 如果您的交互式会话进展顺利,它会在登录到每台设备并配置每台设备上的时钟和时区时打印出会话。在本实验结束时,您的所有设备都应该配置为符合您当地时区的正确时间。为了检查配置,让我们配置另一个运行`show`命令并显示它的快速脚本。在 Ubuntu Python automation server 上执行这些任务。您不再需要登录每台设备来运行相同的命令五次。只需从您的服务器上运行它们。 | |   | `pynetauto@ubuntu20s1:~$` `mkdir ssh_labs` | |   | `pynetauto@ubuntu20s1:~$` `cd ssh_labs` | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano display_show.py` | |   | `GNU nano 4.8` `/home/pynetauto/ssh_labs/ display_show.py` | |   | `import time` | |   | `import paramiko` | |   | `ip_addresses = ["192.168.183.10", "192.168.183.20", "192.168.183.101", "192.168.183.102", "192.168.183.133"]` | |   | `username = "pynetauto"` | |   | `password = "cisco123"` | |   | `for ip in ip_addresses` `:` | |   | `ssh_client = paramiko.SSHClient()` | |   | `ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())` | |   | `ssh_client.connect(hostname=ip, username=username, password=password)` | |   | `print("Connected to " + ip + "\n")` | |   | `remote_connection = ssh_client.invoke_shell()` | |   | `output1 = remote_connection.recv(3000)` `# Catches and removes the login prompt output` | |   | `# print(output1.decode('ascii'))` `# remove hash to print the login prompt message` | |   | `# Now send the commands you want to run and display on the screen` | |   | `remote_connection.send("show clock detail\n")` | |   | `time.sleep(2)` | |   | `output2 = remote_connection.recv(6000)` | |   | `print((output2).decode('ascii'))` | |   | `print("-"*80)` | |   | `^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 脚本就像是一个针对多个网络设备的`show`命令工具。现在运行脚本来检查所有设备上的时钟和时区更改。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$ python3 display_show.py` | |   | `Connected to 192.168.183.10` | |   | `show clock detail` | |   | `15:15:23.609 AEST Tue Jan 12 2021` | |   | `Time source is user configuration` | |   | `Summer time starts 02:00:00 AEST Sun Mar 14 2021` | |   | `Summer time ends 02:00:00 AEST Sun Nov 7 2021` | |   | `LAB-R1#` | |   | `--------------------------------------------------------------------------------` | |   | `Connected to 192.168.183.20` | |   | `show clock detail` | |   | `15:15:23.615 AEST Tue Jan 12 2021` | |   | `Time source is user configuration` | |   | `Summer time starts 02:00:00 AEST Sun Mar 14 2021` | |   | `Summer time ends 02:00:00 AEST Sun Nov 7 2021` | |   | `lab-r2#` | |   | `--------------------------------------------------------------------------------` | |   | `[...omitted for brevity]` | | **7** | 当您处于交互模式时,您可以在导入一个库后使用`dir(library_name)`显示每个库可用的模块。在前面的例子中,您已经使用了`paramiko`库中的`SSHClient`和`AutoAddPolicy`模块。 | |   | `>>>` `import paramiko` | |   | `>>>` `dir(paramiko)` | |   | `['AUTH_FAILED', 'AUTH_PARTIALLY_SUCCESSFUL', 'AUTH_SUCCESSFUL', 'Agent', 'AgentKey', 'AuthHandler', 'AuthenticationException' , 'AutoAddPolicy', 'BadAuthenticationType',` | |   | `[...omitted for brevity]` | |   | `'SFTP_NO_CONNECTION', 'SFTP_NO_SUCH_FILE', 'SFTP_OK', 'SFTP_O P_UNSUPPORTED', 'SFTP_PERMISSION_DENIED', 'SSHClient', 'SSHConfig', 'SSHException', 'SecurityOptions', 'ServerInterface', 'Su bsystemHandler', 'Transport', 'WarningPolicy', '__all__', '__author__', '__builtins__', '__cached__',` | |   | `[...omitted for brevity]` | |   | `'sftp_client', 'sftp_file', 'sftp_handle', 'sftp_server', 'sftp_si', 'ssh_exception', 'ssh_gss', 'sys', 'transport',  'util']` | | **8** | 要在`paramiko`中查看每个模块的包,您可以首先导入每个模块,然后再次使用`dir(module_name)`查看该模块的一些细节。 | |   | `>>>` `from paramiko import SSHClient` | |   | `>>>` `dir(SSHClient)` | |   | `['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__ getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__' , '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_aut h', '_families_and_addresses', '_key_from_filepath', '_log', 'close', 'connect', 'exec_command', 'get_host_keys', 'get_transp ort', 'invoke_shell', 'load_host_keys', 'load_system_host_keys', 'open_sftp', 'save_host_keys', 'set_log_channel', 'set_missi ng_host_key_policy']` | |   | `>>>` `from paramiko import AutoAddPolicy` | |   | `>>>` `dir(AutoAddPolicy)` | |   | `['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__' , '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce _ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'missing_host_key']` | ## paramiko 实验 2:在没有用户交互的情况下在 Cisco 设备上配置 NTP 服务器(NTP 实验) 在本实验中,您将配置`LAB-R1`和`lab-r2`,从两个外部文件中读取信息。第一个文件每行包含每个路由器的 IP 地址,另一个文件包含用户名和密码。当您使用外部文件时,网络管理员不必坐在设备的远程控制台前以交互方式键入信息。在下一章,你将学习如何使用 Linux `cron`作为一个调度器来运行你的 Python 应用,而不需要你的交互,这样你的应用就变成了机器人。与 Cisco ACI 和 Red Hat Ansible Tower 提供的花哨功能相比,使用 Python 脚本作为应用和使用 Linux `cron`作为默认调度程序可能看起来相当简陋(对您的公司没有成本)和原始(没有美化的 GUI)。尽管如此,你必须了解`cron`是如何工作的,以及如何定制你的环境,因为每个客户都有不同的网络环境。没有哪两个网络在规模和复杂程度上是相同的。毕竟,计划者就是计划者。见图 14-2 。 ![img/492721_1_En_14_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Fig2_HTML.jpg) 图 14-2。 SSH paramiko 实验 2,使用中的设备 您需要打开 IP 服务服务器`centos8s1`的电源,为路由器提供 NTP 服务。对于本实验,您需要打开`ubuntu20s1`、`centos8s1`、`LAN-R1`和`lab-r2`的电源,并打开`LAB-SW1`和`lab-sw2`的电源,以连接两台路由器。在这个实验室中,您必须完成许多任务,所以让我们编写脚本并在实验室中运行它。 | # | 工作 | | --- | --- | | **1** | 如果您还没有创建`ssh_labs`目录,在`ubuntu20s1`服务器上,按照以下步骤为 SSH labs 创建一个新目录,然后在同一个目录中创建两个外部文件: | |   | `pynetauto@ubuntu20s1:~/telnet_labs$``cd` | |   | `pynetauto@ubuntu20s1:~$` `mkdir ssh_labs` | |   | `pynetauto@ubuntu20s1:~$` `cd ssh_labs` | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano routerlist` | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `cat routerlist` | |   | `192.168.183.10` | |   | `192.168.183.20` | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano adminpass` | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `cat adminpass` | |   | `pynetauto` | |   | `cisco123` | | **2** | 我已经做了谷歌搜索,复制了一个示例`ssh_client.py`脚本,并修改了内容以适应我的需要。您可以到下面的站点获取基本模板 SSH 脚本,开始编写 SSH 脚本。 | |   | URL: [`https://gist.github.com/ghawkgu/944017`](https://gist.github.com/ghawkgu/944017) | |   | ssh _ client . py | |   | `#!/usr/bin/env python` | |   | `import paramiko` | |   | `hostname = 'localhost'` | |   | `port = 22` | |   | `username = 'foo'` | |   | `password = 'xxxYYYxxx'` | |   | `if __name__ == "__main__":` | |   | `paramiko.util.log_to_file('paramiko.log')` | |   | `s = paramiko.SSHClient()` | |   | `s.load_system_host_keys()` | |   | `s.connect(hostname, port, username, password)` | |   | `stdin, stdout, stderr = s.exec_command('ifconfig')` | |   | `print stdout.read()` | |   | `s.close()` | |   | 该脚本将在两台路由器上配置 NTP 服务器,并在网络上启用时间同步。NTP 在企业网络中起着重要作用,它可以保持所有设备的时间同步,因此系统日志和报告服务器上的时间戳精确到秒。因为这不是一本网络书籍,所以我不会对 stratum 和 NTP 特性进行过多的描述,但是在这里要注意一些事情。首先,思科和许多厂商的网络设备不信任运行在 Windows 机器上的微软 W32tm 服务。第二,某些 Cisco 网络和服务器设备的时间必须设置为最低层,以作为可靠的时间源。通常,该值等于或小于第五层,下层是更值得信任的时间。 | |   | 修改基本`paramiko`登录配置。完成后,它应该类似于以下脚本: | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano ssh_ntp_lab.py` | |   | `GNU nano 4.8`**/home/pynetato/ssh _ labs/ssh _ NTP _ lab . py** | |   | `#!/usr/bin/env python3` | |   | `import paramiko` `# import paramiko library` | |   | `import time` `# import time module` | |   | `from datetime import datetime` `# import datetime module from datetime library` | |   | `t_ref = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")` `# Time reference in desired time format` | |   | `file1 = open("routerlist")` `# open routerlist as file1` | |   | `for line in file1:` `# for loop for router ip address` | |   | `print(t_ref)` `# print time reference` | |   | `print ("Now logging into " + (line))` `# print statement` | |   | `ip_address = line.strip()` `# remove any white spaces` | |   | `file2= open("adminpass")` `# open adminpass as file2` | |   | `for line1 in file2:` `# read the first line(admin ID) in file 2` | |   | `username = line1.strip()` `# remove any white spaces` | |   | `for line2 in file2` `: # read the second line (password) in file2` | |   | `password = line2.strip()` `# remove any white spaces` | |   | `ssh_client = paramiko.SSHClient()` `# Create paramiko SSH client object` | |   | `ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())` `# Automatically accept host key policy` | |   | `ssh_client.connect(hostname=ip_address,username=username,password=password)` `# SSH connection objects` | |   | `print ("Successful connection to " + (ip_address) +"\n")` `# print statement` | |   | `print ("Now completing following tasks : " + "\n")` `# print statement` | |   | `remote_connection = ssh_client.invoke_shell()` `# invoke shell session` | |   | `output1 = remote_connection.recv(3000)` `# Catches and removes the login prompt output` | |   | `# print(output1.decode('ascii'))` `# remove hash to print the login prompt message` | |   | `remote_connection.send("configure terminal\n")` `# Move to configuration mode` | |   | `print ("Configuring NTP Server")` `# print statement` | |   | `remote_connection.send("ntp server 192.168.183.130\n")` `# configure NTP server` | |   | `remote_connection.send("end\n")` `# go back to exec privilege mode` | |   | `remote_connection.send("copy running-config start-config\n")` `# send save command` | |   | `print ()` `# print remote_connections` | |   | `time.sleep(2)` `#` | |   | `output2 = remote_connection.recv(6000)` `# capture session in output variable` | |   | `print((output2).decode('ascii'))` `# print output using ASCII decoding` | |   | `print (("Successfully configured your device & Disconnecting from ") + (ip_address))` `# Print statement` | |   | `ssh_client.close` `# Close SSH connection` | |   | `time.sleep(2)` `# Pause for 2 seconds` | |   | `file1.close()` `# Close file1` | |   | `file2.close()` `# Close file2` | |   | `^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` | | **3** | 由于我们只有两台设备需要检查,这次您可以手动检查端口。登录`LAB-R1`和`lab-r2`,然后运行`show control-plane host open-ports`命令,检查 SSH 端口(22)是否正常工作并处于`LISTEN`状态。 | |   | `LAB-R1#` `show control-plane host open-ports` | |   | `Active internet connections (servers and established)` | |   | `Prot         Local Address        Foreign Address     Service                State` | |   | `tcp          *:22                 *:0                 SSH-Server             LISTEN` | |   | `tcp          *:23                 *:0                 Telnet                 LISTEN` | |   | `udp          *:56827              *:0                 udp_transport Server   LISTEN` | |   | `lab-r2#` `show control-plane host open-ports` | |   | `Active internet connections (servers and established)` | |   | `Prot         Local Address        Foreign Address     Service                State` | |   | `tcp          *:22                 *:0                 SSH-Server             LISTEN` | |   | `tcp          *:23                 *:0                 Telnet                 LISTEN` | |   | `udp          *:18999              *:0                 udp_transport Server   LISTEN` | |   | 或者,您可以修改`display_show.py`文件并从 Python 文件中获取信息。下载`chapter 14_codes.zip`中包含的`display_show_control_plane.py`,使用它可以得到相同的结果。 | |   | 源代码下载网址: [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) | | **4** | 现在,在运行脚本之前,检查 NTP 服务是否在`centos8s1`上正确运行。如果您发现 NTP 服务没有运行,您必须在运行脚本之前解决该问题。在 CentOS 8.1 上,我们之前在第八章安装了`chronyd`,作为 NTP 服务器。以下是解决`chronyd`常见问题的命令。下一步,我们必须通过修改`chrony.conf`文件来完成服务器配置,使其成为一个功能服务器。 | |   | `[pynetauto@centos8s1 ~]$` `systemctl status chronyd` | |   | `[pynetauto@centos8s1 ~]$` `systemctl restart chronyd` | |   | `[pynetauto@centos8s1 ~]$` `systemctl enable chronyd` | |   | `[pynetauto@centos8s1 ~]$` `sudo firewall-cmd --add-port=123/udp --permanent` | |   | `[pynetauto@centos8s1 ~]$` `sudo firewall-cmd --reload` | |   | 如果您忘记安装`chrony`,请使用`dnf install`命令进行安装。 | |   | `[pynetauto@centos8s1 ~]$` `dnf install chrony` | |   | 另外,检查 NTP 服务器上的当前时间。 | |   | `[pynetauto@centos8s1 ~]$` `date` | |   | `Tue Jan 12 17:13:16 AEDT 2021` | |   | 对于`chronyd`,您必须从这里指定的 URL 获取您的本地区域附近的 NTP 服务器列表,并且您必须更新 NTP 服务器池,如图 14-1 所示。确保用散列(`#`)注释掉第一个默认行。请确保从以下站点选择离您最近的公共 NTP 服务器: | |   | URL: [`https://www.pool.ntp.org/en/`](https://www.pool.ntp.org/en/) | |   | 在`sudo`模式下打开文件,修改`/etc/`下的`chrony.conf`文件。 | |   | `[pynetauto@centos8s1 ~]$` `sudo nano /etc/chrony.conf` | |   | `GNU nano 4.8` `/etc/chrony.conf` | |   | `# Use public servers from the pool.ntp.org project.` | |   | `# Please consider joining the pool (` `http://www.pool.ntp.org/join.html).` | |   | `# pool 2.centos.pool.ntp.org iburst` | |   | `server 0.oceania.pool.ntp.org` | |   | `server 1.oceania.pool.ntp.org` | |   | `server 2.oceania.pool.ntp.org` | |   | `server 3.oceania.pool.ntp.org` | |   | `# Record the rate at which the system clock gains/losses time.` | |   | `driftfile /var/lib/chrony/drift` | |   | `[...omitted for brevity]` | |   | `^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` | |   | 当您在同一个配置文件上时,转到该行的末尾,确认您的网络已被允许从本地网络访问此 NTP 服务器。如果您已经在第八章中对此进行了配置,它应该已经在那里了。 | |   | `[... omitted for brevity]` | |   | `# Allow NTP client access from the local network.` | |   | `allow 192.168.183.0/24` | | **5** | 如果一切看起来都井然有序,返回到`ubuntu8s1` Python 服务器并运行`ssh_ntp_lab.py`脚本。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python ssh_ntp_lab.py` | |   | `2021-01-12_17-31-59` | |   | `Now logging into 192.168.183.10` | |   | `Successful connection to 192.168.183.10` | |   | `Now completing following tasks :` | |   | `Configuring NTP Server` | |   | `configure terminal` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `LAB-R1(config)#ntp server 192.168.183.130` | |   | `LAB-R1(config)#end` | |   | `LAB-R1#copy running-config start-config` | |   | `Destination filename [start-config]?` | |   | `Successfully configured your device & Disconnecting from 192.168.183.10` | |   | `2021-01-12_17-31-59` | |   | `Now logging into 192.168.183.20` | |   | `Successful connection to 192.168.183.20` | |   | `Now completing following tasks :` | |   | `Configuring NTP Server` | |   | `configure terminal` | |   | `Enter configuration commands, one per line.  End with CNTL/Z.` | |   | `lab-r2(config)#ntp server 192.168.183.130` | |   | `lab-r2(config)#end` | |   | `lab-r2#copy running-config start-config` | |   | `Destination filename [start-config]?` | |   | `Successfully configured your device & Disconnecting from 192.168.183.20` | | **6** | 一旦 NTP 脚本成功运行,使用新的`show`命令修改`display_show.py`脚本并运行它。在这个实验室中,我将新的`show`命令脚本命名为`display_show_ntp.py`。要检查 NTP 状态,请在脚本中包含`show ntp status`命令。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `cp display_show.py display_show_ntp.py` | |   | 您可以随时在 CLI 中检查状态,以便快速验证。 | |   | `LAB-R1#` `show ntp status` | |   | `lab-r2 #` `show ntp status` | |   | 如果您看到如下所示的类似 NTP 状态消息,您的路由器的时间已经与 Linux NTP 服务器的时间(192.168.183.130)同步。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python3 display_show_ntp.py` | |   | `Connected to 192.168.183.10` | |   | `show ntp status` | |   | `Clock is synchronized, stratum 5, reference is 192.168.183.130` | |   | `nominal freq is 1000.0003 Hz, actual freq is 1000.0003 Hz, precision is 2**15` | |   | `ntp uptime is 191100 (1/100 of seconds), resolution is 1000` | |   | `reference time is E3A7C697.1089E8E5 (06:56:23.064 UTC Tue Jan 12 2021)` | |   | `clock offset is 9948.5451 msec, root delay is 7.75 msec` | |   | `root dispersion is 16599.57 msec, peer dispersion is 2.51 msec` | |   | `loopfilter state is 'SPIK' (Spike), drift is 0.000499999 s/s` | |   | `system poll interval is 64, last update was 375 sec ago.` | |   | `--------------------------------------------------------------------------------` | |   | `Connected to 192.168.183.20` | |   | `show ntp status` | |   | `Clock is synchronized, stratum 5, reference is 192.168.183.130` | |   | `nominal freq is 1000.0003 Hz, actual freq is 1000.0003 Hz, precision is 2**15` | |   | `ntp uptime is 190000 (1/100 of seconds), resolution is 1000` | |   | `reference time is E3A7C6A4.121D0A5D (06:56:36.070 UTC Tue Jan 12 2021)` | |   | `clock offset is 2100.9149 msec, root delay is 12.98 msec` | |   | `root dispersion is 7958.81 msec, peer dispersion is 4.38 msec` | |   | `loopfilter state is 'CTRL' (Normal Controlled Loop), drift is 0.000499999 s/s` | |   | `system poll interval is 64, last update was 736sec ago.` | |   | `--------------------------------------------------------------------------------` | 您已经成功地在`LAB-R1`和`lab-r2`路由器上配置了 NTP 服务器,并与网络中的 Linux NTP 服务器同步了时间。此外,不需要一次输入一个 IP 地址和管理员凭证;在运行脚本之前,您将它们预加载到两个独立的文件中。这将在第八章的`cron`实验室派上用场。密码安全和密码库是重要的主题;然而,它们超出了本书的范围。现在,让我们快速进入下一个 SSH 实验,将我们的设备备份到我们的 TFTP 服务器。 ## paramiko 实验 3:创建一个交互式 paramiko SSH 脚本,将正在运行的配置保存到 TFTP 服务器 在本实验中,您将使用 Python 的`input`函数和`getpass`模块制作一个交互式工具来获取网络管理员的 ID 和密码。为了简化实验,脚本将使用一个包含所有 IP 地址的列表,备份文件将保存到运行在`centos8s1` (192.168.183.130)上的 TFTP 服务器。通过简单的修改,您可以定制和制作操作工具,以节省时间和资源。在本实验中,您只对五台设备进行备份,但是想象一下,如果像在一些客户的网络中一样,需要在 500 台设备上完成这项工作。此外,如果您想使用自动调度程序每天运行这样的脚本,工程师们不再需要坐在他们的计算机前进行备份。当然,也有企业级网络设备备份解决方案,但我们希望通过在我们的实验室中重新创建并运行这些工具,来深入了解这个主题,并了解这些工具的工作原理。见图 14-3 。 ![img/492721_1_En_14_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Fig3_HTML.jpg) 图 14-3。 SSH paramiko 实验 3,使用中的设备 开箱即用的供应商解决方案具有卓越的功能,适合大多数规模庞大的标准化客户环境。尽管如此,您几乎总是需要一些定制,以使解决方案在真实的生产环境中更加通用和有用。没有进一步的讨论,让我们写一个交互式工具 SSH 到我们的路由器和交换机,并采取一些备份。 | # | 工作 | | --- | --- | | **1** | 作为第一步,检查是否可以从 Python 自动化服务器 ping 所有网络设备。这一次,您将再次使用`fping`并通过一个命令检查连通性。如果您仍然没有安装`fping`,现在使用以下命令进行安装: | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `sudo apt-get install fping` | |   | 成功安装`fping`后,运行如下命令来确认您的服务器的网络连接: | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `fping 192.168.183.10 192.168.183.20 192.168.183.101 192.168.183.102 192.168.183.133` | |   | `192.168.183.10 is alive` | |   | `192.168.183.20 is alive` | |   | `192.168.183.133 is alive` | |   | `192.168.183.102 is alive` | |   | `192.168.183.101 is alive` | | **2** | 您已经确认了网络连接,现在让我们来确认我们之前安装的 TFTP 服务是否在`centos8s1`多 IP 服务服务器上正常工作。使用以下命令检查 Linux 服务器上 TFTP 服务的运行状态。 | |   | TFTP ( `xinetd`)处于活动状态,并且在监听模式下与端口 69 一起正常工作。 | |   | `[pynetauto@centos8s1 ~]$` `systemctl is-active xinetd.service` | |   | `active` | |   | `[pynetauto@centos8s1 ~]$ sudo netstat -anp | grep 69` | |   | `[sudo] password for pynetauto:` | |   | `udp        0      0 0.0.0.0:69           0.0.0.0:*                        1241/xinetd` | |   | `udp6       0      0 :::69                   :::*` | |   | `[... omitted for brevity]` | ![img/492721_1_En_14_Figc_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Figc_HTML.gif) 如果您的服务器的端口 69 没有列出,您必须使用以下命令使用`ufw`命令来允许您的服务器上的端口: `[pynetauto@centos8s1 ~]$ sudo ufw allow 69/udp` | # | 工作 | | --- | --- | | **3** | 现在,为了让这个实验更加有趣,我们想测量端到端任务从开始到结束需要多长时间,这样我们就可以测量脚本的速度,并根据需要调整睡眠计时器。正如您已经在`paramiko`和`telnet`脚本中观察到的,已经添加了`time.sleep(s)`代码,以允许虚拟路由器和交换机有足够的响应时间,并且通过`netmiko`脚本,许多计时器都内置到库中。然而,它并不总是 100%准确,所以有时,你仍然需要使用睡眠定时器来定制不同的设备。 | |   | 当你创建应用时,你必须开发一些小创意和小工具来使你的 Python 脚本更有价值。下面显示的计时器收费脚本是一个建议,这个工具测量从 1 到 1000 万计数所需的时间。你可以调整数字,看看你的电脑计算数字的速度有多快。您还可以将您的函数或任务放在脚本中间,以测量脚本运行的速度。 | |   | 在 Python 中,一个函数处理数据的速度比另一个函数快得多,花时间测试脚本的速度是值得的。毕竟,Python 并不以其速度著称,但通常是 Python 编码人员选择了错误的模块或工具,并给脚本增加了延迟。此外,在网络自动化领域,速度很重要,但不是 Python 应用最关键的部分。通常精度比速度更重要。如果你真的需要速度,总有一种最接近机器语言的 C 编程语言。无论如何,在您的服务器上编写以下代码,并检查这个简单的脚本是如何工作的。虽然这是一个简单的脚本,但是许多简单的脚本构成了您的应用的基础。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano timer_tool.py` | |   | `GNU nano 4.8` `/home/pynetauto/ssh_labs/timer_tool.py` | |   | `import time` `# Import time module` | |   | `start_timer = time.mktime(time.localtime())` `# Start time` | |   | `########## REPLACE ME! #########` | |   | `big_number = range(10000000)` `# Put some function or task to run here` | |   | `for i in big_number:` | |   | `print(i,end=" ")` | |   | `# print(" ".join(str(i) for i in big_number))` `# This is a join method to print the number, can replace above two lines of code` | |   | `##############################` | |   | `total_time = time.mktime(time.localtime()) - start_timer` `# End time minus start time` | |   | `print("Total time : ", total_time, "seconds")` `# Total time format` | |   | `^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` | | **4** | 猜猜你的电脑从 1 数到 1000 万需要多长时间?在您的`ubuntu20s1` Python 服务器上,通过键入`python timer_tool.py`运行计时器工具。对于测试计算机来说,从 1 数到 1000 万用了整整 22 秒。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python timer_tool.py` | |   | `[...omitted for brevity]` | |   | `9999974 9999975 9999976 9999977 9999978 9999979 9999980 9999981 9999982 9999983 9999984 9999985 9999986 9999987 9999988 9999989 9999990 9999991 9999992 9999993 9999994 9999995 9999996 9999997 9999998 9999999` | |   | `Total time :  22.0 seconds` | | **5** | 为了使我们的脚本能够与网络管理员交互,让我们使用`getpass`库的`getpass`模块制作一个用户名和密码收集器工具。使用`getpass`模块,我们可以隐藏输入的密码,并且通过一些修改,我们可以让用户再次输入密码,以验证第一个密码。密码经常输入错误,我们可以通过将`if`语句合并到我们的密码脚本中来解决这个问题。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano password_tool.py` | |   | `GNU nano 4.8` `/home/pynetauto/ssh_labs/password_tool.py` | |   | `from getpass import getpass` `# Import getpass module from getpass library` | |   | `def get_credentials()` `:# Create get_credentials function` | |   | `#Prompts for and returns a username and password` `# Comment` | |   | `username = input("*Enter Network Admin ID : ")` `# Prompt user for username` | |   | `password = None` `# Set original password to None` | |   | `while not password:` `# Keep prompting til password is entered` | |   | `password = getpass("*Enter Network Admin PWD : ")` `# Prompt for password` | |   | `password_verify = getpass("**Confirm Network Admin PWD : ")` `# Verify password again` | |   | `if password != password_verify:` `# If password fails to verify, run the following script` | |   | `print("! Network Admin Passwords do not match. Please try again.")` `# Inform the user of mismatch` | |   | `password = None` `# Reset password to None and ask for the password again` | |   | `print(username, password)` `# For testing purpose only, remove this when applied to the script` | |   | `return username, password` `#returns username and password` | |   | `get_credentials()` `# Run get_credentials() 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** | 请创建一个密码工具脚本,并从您的`ubuntu20s1`服务器运行它。出于测试目的,我们将在这里打印用户名和密码,但您必须从功能中删除`print (` `username` `,` `password` `)`命令,并在按下回车键后进入静默状态。根据需要注释掉`print`语句。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python password_tool.py` | |   | `*Enter Network Admin ID :` `pynetauto` | |   | `*Enter Network Admin PWD :********` | |   | `**Confirm Network Admin PWD : ********` | |   | `pynetauto cisco123` | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` | | **7** | 现在,您已经开发了两个迷你工具,让我们编写交互代码来备份我们设备的实验室网络。完成`paramiko`脚本后,您的脚本应该类似于下面这样。详细的解释嵌入在代码中,所以在完整地编写脚本之前,先研究一下代码。如果你遇到问题,参考源代码,但只有在必要的时候。通常大多数代码是由你在键盘上输入的,而不是剪切和粘贴的! | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano interactive_backup.py` | |   | `GNU nano 4.8` `/home/pynetauto/ssh_labs/interactive_backup.py` | |   | `#!/usr/bin/env python3 # for Linux system to run this script as python3 file` | |   | `import re # import regular expression module` | |   | `import time # import time module` | |   | `import paramiko # import paramiko library` | |   | `from datetime import datetime # import datetime module from datetime library` | |   | `from getpass import getpass # Import getpass module from getpass library` | |   | `t_ref = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") # Time reference to be used for file name` | |   | `device_list = ["192.168.183.10", "192.168.183.20", "192.168.183.101", "192.168.183.102", "192.168.183.133"] # Device list` | |   | `start_timer = time.mktime(time.localtime()) # Start time` | |   | `def get_credentials():# Define a function called get_credentials` | |   | `#Prompts for, and returns a username and password # comment` | |   | `global username # Make username global, so it can be used throughout this script` | |   | `global password # Make password global, so it can be used throughout this script` | |   | `username = input("*Enter Network Admin ID : ") # Enter username` | |   | `password = None # set password default value to None` | |   | `while not password: # Until password is entered` | |   | `password = getpass("*Enter Network Admin PWD : ") # Enter the password first time` | |   | `password_verify = getpass("**Confirm Network Admin PWD : ") # Get password for verification, second time` | |   | `if password != password_verify: # Password verification` | |   | `print("! Network Admin Passwords do not match. Please try again.") # Informational` | |   | `password = None # Set password to None` | |   | `return username, password # Optional, returns username and password` | |   | `get_credentials()# Run get_credentials function, to save username and password` | |   | `for ip in device_list: # for loop to grab device IP addresses` | |   | `print(t_ref) # Comment for user information` | |   | `print("Now logging into " + (ip)) # Informational` | |   | `ssh_client = paramiko.SSHClient()# Initiate paramiko SSH client session as ssh_client` | |   | `ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # Accept missing host key policy` | |   | `ssh_client.connect(hostname=ip,username=username,password=password) # SSH connection with credentials` | |   | `print("Successful connection to " + (ip) +"\n") # Informational` | |   | `print("Now making running-config backup of " + (ip) + "\n") # Comment for user information` | |   | `remote_connection = ssh_client.invoke_shell() # Invoke shell` | |   | `time.sleep(3) # Pause 3 seconds to invoke shell` | |   | `remote_connection.send("copy running-config tftp\n") # Send copy command` | |   | `remote_connection.send("192.168.183.130\n") # Respond to TFTP server IP request, TFTP IP` | |   | `remote_connection.send((ip)+ ".bak@" + (t_ref)+ "\n") # Respond to backup file name request` | |   | `time.sleep(3) ## Pause for 3 seconds, give device to respond` | |   | `print() # Print screen` | |   | `time.sleep(3) # Pause for 3 seconds, give time to process data` | |   | `output = remote_connection.recv(65535) # Receive output up to 65535 lines` | |   | `print((output).decode('ascii')) #Print output using ASCII decoding method` | |   | `print(("Successfully backed-up running-config to TFTP & Disconnecting from ") + (ip) + "\n") # Print statement to provide an update to the user` | |   | `print("-"*80) # Print ~ 80 times` | |   | `ssh_client.close # Close SSH session` | |   | `time.sleep(1) # Pause for 1 second` | |   | `total_time = time.mktime(time.localtime()) - start_timer # Start time minus current time` | |   | `print("Total time : ", total_time, "seconds") # Print the total time to run the script in seconds` | |   | `^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` | | **8** | 在第八章中,你在`centos8s1`服务器上创建了一个`tftpdir`目录用于 TFTP 文件的共享和存储。作为运行前面脚本的预检查,检查`/var/tftpdir`目录中的文件。在 Linux 中,改变大小的文件通常放在`var`(变量)目录下。 | |   | `[pynetauto@centos8s1 ~]$` `cd /var/tftpdir` | |   | `[pynetauto@centos8s1 tftpdir]$` `pwd` | |   | `/var/tftpdir` | |   | `[pynetauto@centos8s1 tftpdir]$` `ls -lh` | |   | `total 45M` | |   | `-rw-r--r--. 1 pynetauto pynetauto  45M Jul 18 04:59 c3725-adventerprisek9-mz.124-15.T14.bin` | |   | `-rw-rw-r--. 1 tftpuser  tftpuser  1.2K Jan  9 00:07 running-config` | |   | `-rw-rw-r--. 1 tftpuser  tftpuser   100 Jun  8  2020 transfer_file01` | |   | `-rw-r--r--. 1 root      root       100 Jun  8  2020 transfer_file77` | | **9** | 回到 Python 自动化服务器(`ubuntu20s1`)。好了,现在让我们运行交互式备份脚本,通过 SSH 访问每台设备,并在 TFTP 服务器上备份每台设备的运行配置。运行`python interactive_backup.py`命令,输入网络管理员凭证,然后坐下来观察。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python interactive_backup.py` | |   | `*Enter Network Admin ID :` `pynetauto` | |   | `*Enter Network Admin PWD : ********` | |   | `**Confirm Network Admin PWD : ********` | |   | `2021-01-12_18-42-23` | |   | `Now logging into 192.168.183.10` | |   | `Successful connection to 192.168.183.10` | |   | `Now making running-config backup of 192.168.183.10` | |   | `[...omitted for brevity]` | |   | `Destination filename [lab-r1-confg]? 192.168.183.10.bak@2021-01-12_18-42-23` | |   | `!!` | |   | `3953 bytes copied in 2.972 secs (1330 bytes/sec)` | |   | `LAB-R1#` | |   | `Successfully backed-up running-config to TFTP & Disconnecting from 192.168.183.10` | |   | `--------------------------------------------------------------------------------` | |   | `2021-01-12_18-42-23` | |   | `Now logging into 192.168.183.20` | |   | `Successful connection to 192.168.183.20` | |   | `[...omitted for brevity]` | |   | `lab-r2#` | |   | `Successfully backed-up running-config to TFTP & Disconnecting from 192.168.183.20` | |   | `--------------------------------------------------------------------------------` | |   | `2021-01-12_18-42-23` | |   | `Now logging into 192.168.183.101` | |   | `Successful connection to 192.168.183.101` | |   | `[...omitted for brevity]` | |   | `LAB-SW1#` | |   | `Successfully backed-up running-config to TFTP & Disconnecting from 192.168.183.101` | |   | `--------------------------------------------------------------------------------` | |   | `2021-01-12_18-42-23` | |   | `Now logging into 192.168.183.102` | |   | `Successful connection to 192.168.183.102` | |   | `[...omitted for brevity]` | |   | `lab-sw2#` | |   | `Successfully backed-up running-config to TFTP & Disconnecting from 192.168.183.102` | |   | `--------------------------------------------------------------------------------` | |   | `2021-01-12_18-42-23` | |   | `Now logging into 192.168.183.133` | |   | `Successful connection to 192.168.183.133` | |   | `[...omitted for brevity]` | |   | `R1#` | |   | `Successfully backed-up running-config to TFTP & Disconnecting from 192.168.183.133` | |   | `--------------------------------------------------------------------------------` | |   | `Total time :  78.0 seconds` | |   | 将五台实验室设备备份到 TFTP 服务器需要 78 秒。在接下来的`netmiko`实验中,我们还将了解 FTP 的文件管理,但是我们到目前为止学到的概念可以在 FTP 或 SFTP 场景中重用。 | | **10** | 现在重新登录到您的`centos8s1`服务器;然后将您的目录更改为`/var/tftpdir`并运行`ls –lh`命令。您应该可以找到所有带有我们代码中指定的日期和时间戳的设备备份。现在,您已经完成了捕获所有设备的运行配置的 SSH 交互式脚本。我们在这里只使用了五个设备,但是设备的数量将会成倍增加,甚至在生产中您都不必担心。太好了。 | |   | `[pynetauto@centos8s1 tftpdir]$` `ls -lh 192.*` | |   | `-rw-rw-r--. 1 tftpuser tftpuser 4.5K Jan 12 18:43 192.168.183.101.bak@2021-01-12_18-42-23` | |   | `-rw-rw-r--. 1 tftpuser tftpuser 3.8K Jan 12 18:43 192.168.183.102.bak@2021-01-12_18-42-23` | |   | `-rw-rw-r--. 1 tftpuser tftpuser 3.5K Jan 12 18:43 192.168.183.10.bak@2021-01-12_18-42-23` | |   | `-rw-rw-r--. 1 tftpuser tftpuser 1.4K Jan 12 18:43 192.168.183.133.bak@2021-01-12_18-42-23` | |   | 听着... | 现在,您已经通过 SSH 连接和 TFTP 文件传输完成了正在运行的配置备份,从而完成了`paramiko`实验。您可以轻松地调整配置,使用 FTP 或 SFTP 文件传输进行进一步的测试和开发。在本章的后半部分,你将在不同的场景中使用通用的`netmiko`库。 ## Python SSH 实验室:netmiko 是一个优秀的 SSH 库,用于通过 SSH 连接管理网络设备。尽管如此,你必须努力工作来微调你的脚本部分,使其顺利运行。例如,通过 SSH 连接和运行不同命令的时间需要特别注意时间和提示。幸运的是,一位 CCIE 退休工程师已经努力工作,并考虑了我们在使用`paramiko`时可能遇到的许多问题。他开发了一个令人难以置信的叫做`netmiko`的 Python 库。`netmiko`是一个多供应商库,用于简化`paramiko`到网络设备的 SSH 连接;Kirk Byers 开发它是为了支持各种厂商的网络设备。在 Python 中,世界上许多隐藏的技术大师并不害怕分享他们的知识和智慧。在使用了大约两年的`netmiko`之后,我无法感谢 Kirk Byers 将我从`paramiko`的许多陷阱中拯救出来。他已经想到了`paramiko`的 SSH 连接问题。我们不得不享受一个工程师的艺术和汗水的成果,开拓 Python 网络自动化。这里是 Kirk 的官方`netmiko`链接和 GitHub 网站供进一步阅读。还有,`nornir`项目是`netmiko`的扩展;它具有 Ansible 的相同特性,但是是用 Python 编写的。`nornir`超出了本书的范围,但是当你有时间的时候,请上网查一下。 网址: [`https://pynet.twb-tech.com/`](https://pynet.twb-tech.com/) (Kirk Byers 的《网络工程师的 Python》) 网址:【with Netmiko 入门) 网址: [`https://github.com/ktbyers/netmiko/blob/master/netmiko/ssh_dispatcher.py`](https://github.com/ktbyers/netmiko/blob/master/netmiko/ssh_dispatcher.py) (支持的设备列表) 现在,在编写第一个`netmiko` Python 脚本之前,确保已经使用`sudo pip3 install netmiko`安装了`netmiko`库,如下所示: ```py pynetauto@ubuntu20s1:~$ sudo pip3 install netmiko [sudo] password for pynetauto: Collecting netmiko Downloading netmiko-3.2.0-py2.py3-none-any.whl (157 kB) |████████████████████████████| 157 kB 4.8 MB/s Requirement already satisfied: pyserial in /usr/lib/python3/dist-packages (from netmiko) (3.4) [... omitted for brevity] Successfully installed netmiko-3.2.0 scp-0.13.2 textfsm-1.1.0 ``` 在安装完`netmiko`之后,启动 Python 解释器并导入`netmiko`模块。您可以使用`pip3 freeze`命令来检查已安装的库,但是有时即使它被列出,您也无法在系统上运行多个 Python 版本的环境中使用该模块。如果解释器上没有返回错误,那么它已经成功安装,您可以开始编写下面的代码了: ```py pynetauto@ubuntu20s1:~/ssh_labs$ pip3 freeze ... netmiko==3.3.2 ... pynetauto@ubuntu20s1:~$ 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 netmiko >>> ``` 验证后,按 Ctrl+Z 或键入`exit()`或`quit()`关闭 Python 3 解释器。 ## netmiko 实验 1: netmiko 使用字典来存储设备信息,而不是 JSON 对象 使用 netmiko,您必须以字典格式提供设备信息,其中至少包含基本的 SSH 登录信息,例如设备类型、IP(主机名)、管理员 ID 和密码。您还可以提供其他可选信息,如密码、端口号和登录状态。乍一看,这种字典格式类似于 JavaScript Object Notation (JSON)格式的数据集。`netmiko`设备信息是内置的 Python 字典,不是浏览器和服务器用来发送数据的 JSON 数据集。典型的`netmiko`字典通常是这样的: ```py cisco_3850 = { 'device_type': 'cisco_ios', 'host': '10.20.30.40', 'username': 'cisco', 'password': 'password123', 'port' : 8022, # optional, if not used defaults to 22 'secret': 'secret123', # optional, if not used defaults to '' } ``` 如果你想像前面的`paramiko`例子一样把前面的字典变平,你可以这样写字典。尽管如此,以前的结构化信息看起来更好看。 ```py cisco_csr1k = {"device_type":"cisco_xe", "ip":"192.168.183.1", "username":"pynetauto", "password":"cisco123"} ``` 我们还可以通过混合一些输入语句和`getpass`函数来请求用户输入,从而增强`netmiko`字典,如下所示: ```py from netmiko import ConnectHandler from getpass import getpass device1 = { 'device_type': 'cisco_ios', 'ip': input('IP Address : '), 'username': input('Enter username : '), 'password': getpass('SSH password : '), } ``` 在进入`netmiko` lab 1 之前,我们将确认`netmiko`字典不是 JSON 对象。让我们快速地在 Python 解释器中输入这段代码来确认这个事实。 ![img/492721_1_En_14_Figd_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Figd_HTML.gif) 打开 Python 解释器,在解释器窗口中输入以下内容。确保在创建字典时使用双引号,`device1`。Python 会自动把它们转换成单引号,你就知道为什么了。首先,检查一个`netmiko`字典的属性。 `>>>` `device1 = {"device_type":"cisco_ios", "ip":"192.168.183.10", "username":"pynetauto", "password":"cisco123"}` `>>>` `print(device1)` `{'device_type': 'cisco_ios', 'ip': '192.168.183.10', 'username': 'pynetauto', 'password': 'cisco123'}` `>>>` `print(type(device1))` `` `>>> dir(device1)` `['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__',` `(omitted for brevity)` `'update', 'values']` 使用`dumps`方法将`device1`字典转换成 JSON 格式。当我们打印转换后的 JSON 对象时,它们看起来像一个 Python 字典,但是关键的区别是所有的键和值集都用双引号括起来。 `>>>` `import json` `>>>` `device2 = json.dumps(device1)` `>>>` `print(device2)` `{"device_type": "cisco_ios", "ip": "192.168.183.10", "username": "pynetauto", "password": "cisco123"}` `>>>` `print(type(device2))` `` 乍一看,您可能会认为 JSON 对象与`netmiko` dictionary 对象相同,但它是一个字符串对象,正如`dir()`方法所揭示的那样。JSON 对象属于 Python 内置的`str`类,当然`netmiko`字典属于内置的`dict`类。 `>>>` `dir(device2)` `['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', (omitted for brevity)` `, 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']` `# Quickly check if device1 is the same as device2` `>>>` `if device1 == device2:` `...` `print(True)` `...` `else:` `...` `print(False)` `...` `False` 在`netmiko`实验 1 中,您将学习如何在 Cisco 路由器上使用 TCL shell 命令创建目录和文件,以便您可以创建虚拟文件来练习从路由器的`flash:`中删除文件。您将使用 IOSv 路由器`LAB-R1`,以保持简单,因为您将编写的实际脚本开始变得复杂,尤其是如果您来自非思科网络背景。尝试将注意力集中在概念上,而不是 IOSv 映像有多旧。正如我在第一章中解释的那样,最新和最棒的工具是好的,但是它们不需要学习网络或 Python 网络自动化的基本概念。见图 14-4 。 ![img/492721_1_En_14_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Fig4_HTML.jpg) 图 14-4。 SSH netmiko 实验 1,正在使用的设备 本实验的目的是让您练习使用 Python 脚本从 Cisco 设备的`flash` `:`中删除文件,而不是直接连接到 Cisco 设备。您将首先格式化`LAB-R1`的闪存,使用`mkdir`命令创建一个目录,并使用传统的 TCL shell 命令创建一些要删除的文件。我们应该能够编写 Python 代码,允许我们删除旧文件,还允许我们导航到一个文件夹来定位我们正在寻找的文件,以便 Python 应用可以根据需要删除这些文件。虽然您可能看不出与您的工作有任何关系,但这个特性在 IOS 升级应用开发中非常有用。 | # | 工作 | | --- | --- | | **1** | 打开`LAB-R1`的控制台窗口,格式化磁盘 0,这样就没有文件了。 | |   | `LAB-R1#``format flash` | |   | `Format operation may take a while. Continue? [confirm]` | |   | `Format operation will destroy all data in "flash:".  Continue? [confirm]` | |   | `Format: All system sectors written. OK...` | |   | `Format: Total sectors in formatted partition: 4193217` | |   | `Format: Total bytes in formatted partition: 2146927104` | |   | `Format: Operation completed successfully.` | |   | `Format of flash0: complete` | |   | `LAB-R1#show flash:` | |   | `-#- --length-- -----date/time------ path` | |   | `2142711808 bytes available (8192 bytes used)` | | **2** | 使用`mkdir flash:/directory_name`在`flash` `:`下创建一个目录。 | |   | `LAB-R1#` `mkdir flash:/old_files` | |   | `Create directory filename [old_files]?` | |   | `Created dir flash0:/old_files` | |   | `LAB-R1#dir` | |   | `Directory of flash0:/` | |   | `4  drw-           0  Jan 12 2021 08:09:26 +00:00  old_files` | |   | `2142720000 bytes total (2142707712 bytes free)` | | **3** | 现在使用传统的 TCL shell 创建四个文件,如下所示。两个文件将在`flash` `:`下创建,另外两个文件将在`flash:old_files/`下创建。 | |   | `LAB-R1#` `tclsh` | |   | `LAB-R1(tcl)#` `puts [open "flash:Delete_me_1.bin" w+]` | |   | `file0` | |   | `LAB-R1(tcl)#` `puts [open "flash:Don't_delete_me_1.txt" w+]` | |   | `file1` | |   | `LAB-R1(tcl)#` `puts [open "flash:old_files/Delete_me_2.bin" w+]` | |   | `file2` | |   | `LAB-R1(tcl)#` `puts [open "flash:old_files/Don't_delete_me_2.txt" w+]` | |   | `file3` | |   | `LAB-R1(tcl)#` `dir` | |   | `Directory of flash0:/` | |   | `4  drw-           0  Jan 12 2021 08:09:26 +00:00  old_files` | |   | `5  -rw-           0  Jan 12 2021 08:11:16 +00:00  Delete_me_1.bin` | |   | `6  -rw-           0  Jan 12 2021 08:11:24 +00:00  Don't_delete_me_1.txt` | |   | `2142720000 bytes total (2142707712 bytes free)` | |   | `LAB-R1#` `dir flash:/old_files/` | |   | `Directory of flash0:/old_files/` | |   | `7  -rw-           0  Jan 12 2021 08:11:32 +00:00  Delete_me_2.bin` | |   | `8  -rw-           0  Jan 12 2021 08:11:40 +00:00  Don't_delete_me_2.txt` | |   | `2142720000 bytes total (2142707712 bytes free)` | | **4** | 我们已经在`LAB-R1`的`flash:`上创建了一个虚拟目录和虚拟 IOS ( `.bin`)文件。让我们编写一个很酷的 SSH 脚本,使用`netmiko`库登录到`LAB-R1`,然后使用 Python 脚本删除一些`.bin`文件。是的,您可以登录到路由器的 CLI 并删除该文件,但是您可能会问,为什么要这么麻烦地用 Python 脚本创建它呢?第一,作为一个想为网络自动化写代码的读者,你必须做艰苦的工作来享受你的劳动成果。第二,这个剧本可以跟你顶嘴,可以团队合作。最后,想象一下,如果您要处理 100 台路由器或交换机,而不是一台设备。另外,想象一下你必须定期做这种训练,作为你工作的一部分。Python network automation 的真正威力在于它的循环(`for`循环和`while`循环),在下一个实验中,让我们将这个脚本修改为较新的版本,并在多个设备上使用它。 | |   | 查看引用嵌入描述的脚本。编写完代码后,转到第 5 步运行脚本。 | |   | `GNU nano 4.8` `/home/pynetauto/ssh_labs/netmiko_delete_me.py` | |   | `#!/usr/bin/env python3` | |   | `import re` | |   | `from netmiko import ConnectHandler` | |   | `from getpass import getpass` | |   | `import time` | |   | `device1 = {` `#Netmiko dictionary for device1` | |   | `'device_type': 'cisco_ios',` | |   | `'ip': input('IP Address : '),` | |   | `'username': input('Enter username : '),` | |   | `'password': getpass('SSH password : '),` | |   | `}` | |   | `net_connect = ConnectHandler(**device1)` `# Netmiko ConnectHandler object` | |   | `net_connect.send_command("terminal length 0\n")` `# Make terminal length to 0 to display all` | |   | `time.sleep(1)` `# Pause for 1 second` | |   | `dir_flash = net_connect.send_command("dir flash:\n")` `# Send "dir flash:" command` | |   | `print(dir_flash)` `# Display content of dir flash output` | |   | `p30 = re.compile(r'D[0-9a-zA-Z]{4}.*.bin')` `# Regular Expression to capture any file starting with "D", ends with "bin"` | |   | `m30 = p30.search(dir_flash)` `# Search for first match of p30` | |   | `time.sleep(1)` `# Pause for 1 second` | |   | `# net_connect1.enable() (Optional, not required with privilege 15 access)` | |   | `print("!!! WARNING - You cannot reverse this step.")` `# Message to user` | |   | `# If dir_flash contains a string which satisfies p30 (True),` | |   | `# then run this script to delete a file.` | |   | `if bool(m30) == True:` `# If m30 is true` | |   | `print("If you can see 'Delete_me.bin' file, select it and press Enter.")` `# Informational` | |   | `del_cmd = "del flash:/"` `# Partial command 1` | |   | `old_ios = input("*Old IOS (bin) file to delete : ")` `# Partial command 2, select a file under flash:` | |   | `while not p30.match(old_ios) or old_ios not in dir_flash:` `# User input request until correct file name is given` | |   | `old_ios = input("**Old IOS (bin) file to delete : ")` | |   | `command = del_cmd + old_ios` `# Complete command (1 + 2)` | |   | `output = net_connect.send_command_timing` `( # Special netmiko send_command_timing command with timer` | |   | `command_string=command` `,` | |   | `strip_prompt=False` `,` | |   | `strip_command=False` | |   | `)` | |   | `if "Delete filename" in output:` `# if the returned output contains "Delete filename", send "Enter" (change line)` | |   | `output += net_connect.send_command_timing(` | |   | `command_string="\n",` | |   | `strip_prompt=False,` | |   | `strip_command=False` | |   | `)` | |   | `if "confirm" in output:` `# if the returned output contains "confirm", send "y"` | |   | `output += net_connect.send_command_timing(` | |   | `command_string="y",` | |   | `strip_prompt=False,` | |   | `strip_command=False` | |   | `)` | |   | `net_connect.disconnect` `# Disconnect from SSH session` | |   | `print(output)` `# Informational` | |   | `# If None (False), then run this script to search directory for .bin file to delete` | |   | `elif bool(m30) == False:``# If no .bin file starting with D is found under "flash:", run this` | |   | `print("No IOS file under 'flash:/', select the directory to view.")` `# Informational` | |   | `open_dir = input("*Enter Directory name : ")` `# Partial command 1` | |   | `while not open_dir in dir_flash:` `# Ask until correct response is received` | |   | `open_dir = input("** Enter Directory name : ")` `# If file does not exist, request user input again` | |   | `open_dir_cmd = (r"dir flash:/" + open_dir)` `# Completed command` | |   | `send_open_dir = net_connect.send_command(open_dir_cmd)` `# Send the command` | |   | `print(send_open_dir)` `# Informational` | |   | `p31 = re.compile(r'D[0-9a-zA-Z]{4}.*.bin')` `# Regular Expression to capture any file starting with "D", ends with "bin"` | |   | `m31 = p31.search(send_open_dir)` `# Send completed command` | |   | `if bool(m31) == True:` `# If there is a file with the string satisfy p31 expression` | |   | `print("If you see old IOS (bin) in the directory. Select it and press Enter.")` `# Informational` | |   | `del_cmd = "del flash:/" + open_dir + "/"` `# Completed command` | |   | `old_ios = input("*Old IOS (bin) file to delete : ")` `# Enter the .bin file to delete` | |   | `while not p30.match(old_ios) or old_ios not in send_open_dir:` `# User input request until correct file name is given` | |   | `old_ios = input("**Old IOS (bin) file to delete : ")` | |   | `command = del_cmd + old_ios` `# Complete command` | |   | `output = net_connect.send_command_timing(` `# Special netmiko send_command_timing command with timer` | |   | `command_string=command` `,` | |   | `strip_prompt=False,` | |   | `strip_command=False` | |   | `)` | |   | `if "Delete filename" in output:` `# if the returned output contains "Delete filename", send "Enter" (change line)` | |   | `output += net_connect.send_command_timing` `(` | |   | `command_string="\n",` | |   | `strip_prompt=False,` | |   | `strip_command=False` | |   | `)` | |   | `if "confirm" in output:` `# if the returned output contains "confirm", send "y"` | |   | `output += net_connect.send_command_timing(` | |   | `command_string="y",` | |   | `strip_prompt=False,` | |   | `strip_command=False` | |   | `)` | |   | `net_connect.disconnect` `# Disconnect from SSH session` | |   | `print(output)` `# Print content of output` | |   | `else` `: # Both conditions failed to satisfy, exit the script` | |   | `("No IOS found.")` | |   | `exit()` | |   | `net_connect.disconnect` `# Disconnect from SSH session` | |   | `^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** | 现在,导航回您的`ubuntu20s1` (192.168.183.132) Python 服务器并 ping `LAB-R1` (192.168.183.10)以确认设备的连接性。您仍然在使用 SSH 协议,`netmiko`实验室仍然是 SSH 实验室的一部分,所以您应该在`/home/pynetauto/ssh_labs`目录中工作。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `ping 192.168.183.10 -c 3` | |   | `PING 192.168.183.10 (192.168.183.10) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.10: icmp_seq=1 ttl=255 time=7.37 ms` | |   | `64 bytes from 192.168.183.10: icmp_seq=2 ttl=255 time=6.73 ms` | |   | `64 bytes from 192.168.183.10: icmp_seq=3 ttl=255 time=12.9 ms` | |   | `--- 192.168.183.10 ping statistics ---` | |   | `3 packets transmitted, 3 received, 0% packet loss, time 2004ms` | |   | `rtt min/avg/max/mdev = 6.734/9.016/12.948/2.791 ms` | |   | `pynetauto@ubuntu20s1:~/ssh_labs$ ls netmiko*` | |   | `netmiko_delete_me.py` | | **6** | 现在使用`python netmiko_delete_me.py`运行脚本。当脚本返回输出时,选择`.bin`文件,并按 Enter 键将其从`LAB-R1`的闪存中删除。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python netmiko_delete_me.py` | |   | `IP Address : 192.168.183.10` | |   | `Enter username : pynetauto` | |   | `SSH password :` | |   | `Directory of flash0:/` | |   | `4  drw-           0  Jan 12 2021 08:09:26 +00:00  old_files` | |   | `5  -rw-           0  Jan 12 2021 08:11:16 +00:00  Delete_me_1.bin` | |   | `6  -rw-           0  Jan 12 2021 08:11:24 +00:00  Don't_delete_me_1.txt` | |   | `2142720000 bytes total (2142707712 bytes free)` | |   | `!!! WARNING - You cannot reverse this step.` | |   | `If you can see 'Delete_me.bin' file, select it and press Enter.` | |   | `*Old IOS (bin) file to delete : Delete_me_1.bin` | |   | `del flash:/Delete_me_1.bin` | |   | `Delete filename [Delete_me_1.bin]?` | |   | `Delete flash0:/Delete_me_1.bin? [confirm]y` | |   | `LAB-R1#` | | **7** | 现在打开`LAB-R1`的控制台,检查文件`Delete_me_1.bin`是否已经从`flash`的`:`中删除。如果您在这里只看到一个文件和一个文件夹,那么您已经完成了第一项任务。转到步骤 9,重新运行相同的脚本。 | |   | `LAB-R1#` `dir` | |   | `Directory of flash0:/` | |   | `4  drw-           0  Jan 12 2021 08:09:26 +00:00  old_files` | |   | `6  -rw-           0  Jan 12 2021 08:11:24 +00:00  Don't_delete_me_1.txt` | |   | `2142720000 bytes total (2142707712 bytes free)` | | **8** | 第二次运行相同的脚本;这一次,由于`flash` `:`下没有`.bin`文件,所以会提示不同的信息,要求您选择一个目录来搜索`.bin`文件。当你剪切并粘贴目录名时,它会运行脚本来寻找难以捉摸的`.bin`文件并显示结果。当脚本找到`.bin`文件时,继续删除`Delete_me_2.bin`文件。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python netmiko_delete_me.py` | |   | `IP Address :` `192.168.183.10` | |   | `Enter username :` `pynetauto` | |   | `SSH password : ********` | |   | `Directory of flash0:/` | |   | `4  drw-           0  Jan 12 2021 08:09:26 +00:00  old_files` | |   | `6  -rw-           0  Jan 12 2021 08:11:24 +00:00  Don't_delete_me_1.txt` | |   | `2142720000 bytes total (2142707712 bytes free)` | |   | `!!! WARNING - You cannot reverse this step.` | |   | `No IOS file under 'flash:/', select the directory to view.` | |   | `*Enter Directory name :``old_files` | |   | `Directory of flash0:/old_files/` | |   | `7  -rw-           0  Jan 12 2021 08:11:32 +00:00  Delete_me_2.bin` | |   | `8  -rw-           0  Jan 12 2021 08:11:40 +00:00  Don't_delete_me_2.txt` | |   | `2142720000 bytes total (2142707712 bytes free)` | |   | `If you see old IOS (bin) in the directory. Select it and press Enter.` | |   | `*Old IOS (bin) file to delete :``Delete_me_2.bin` | |   | `del flash:/old_files/Delete_me_2.bin` | |   | `Delete filename [/old_files/Delete_me_2.bin]?` | |   | `Delete flash0:/old_files/Delete_me_2.bin? [confirm]y` | |   | `LAB-R1#` | | **9** | 返回到`LAB-R1`并运行`dir flash:old_files/`命令来查看目录内容。如果您在目录下只看到一个文件,则您已经完成了本实验的第二项任务。 | |   | `LAB-R1#` `dir flash:/old_files/` | |   | `Directory of flash0:/old_files/` | |   | `8  -rw-           0  Jan 12 2021 08:11:40 +00:00  Don't_delete_me_2.txt` | |   | `2142720000 bytes total (2142707712 bytes free)` | | **10** | 现在删除工作目录和文件,因为我们已经完成了本实验。 | |   | `LAB-R1#` `delete /recursive /force flash:old_files` | |   | `LAB-R1#delete flash:` `Don't_delete_me_1.txt` | |   | `Delete filename [Don't_delete_me_1.txt]?` | |   | `Delete flash0:/Don't_delete_me_1.txt? [confirm]` | |   | `LAB-R1#` `dir` | |   | `Directory of flash0:/` | |   | `No files in directory` | |   | `2142720000 bytes total (2142711808 bytes free)` | 您现在已经学习了如何编写 Python 代码来删除和搜索路由器的闪存文件。你可以把你的任务变成 Python 脚本,这是 Python 网络自动化的开始。接下来,我们将开发一个端口扫描工具来检查开放的端口,并将其应用到我们的工作中。 ## netmiko 实验 2:使用套接字模块开发一个简单的端口扫描器,然后开发一个 nemiko Disable Telnet 脚本 在本实验中,您将学习如何使用套接字模块开发一个简单的端口扫描工具,然后在您的`netmiko`实验 2 脚本(`netmiko_disable_telnet.py`)中使用该扫描工具来检查打开的端口 23,并使用`netmiko`库禁用这些端口。早些时候,我们在`line vty 0 15`下配置了`transport input all`,允许 Telnet 和 SSH 连接用于所有设备管理。要禁用 Telnet,您必须用`transport input ssh`命令重新配置虚拟电传(`vty`)线路。本实验需要打开所有路由器和交换机的电源。 在本实验结束时,您将能够扫描网络设备以查找任何开放的端口;您将检查端口 23(或 22 或任何其他感兴趣的端口)是否在使用中。通过禁用所有设备上的 Telnet 端口 23 来保护您的设备,并且只允许 SSH 连接用于设备管理。再次强调,尝试将注意力集中在概念上,并开发实现本实验目标所需的工具。见图 14-5 。 ![img/492721_1_En_14_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Fig5_HTML.jpg) 图 14-5。 SSH netmiko 实验 2,使用中的设备 让我们首先开发一个迷你扫描工具,并验证我们的路由器和交换机上的开放端口。然后将扫描工具合并到我们的脚本中,`netmiko_disable_telnet.py`。 | # | 工作 | | --- | --- | | **1** | 首先,您必须使用`socket.socket(family, type)`创建一个 socket 对象,将 family 设置为`socket.AF_INET`,type 设置为`socket.SOCK_STREAM`。根据 Python 套接字编程,`socket.AF_INET`为 IPv4 指定 IP 地址族,`socket.SOCK_STREAM`为 TCP 指定套接字类型。要检查端口状态,使用`socket.connect_ex(dest)`,将套接字作为套接字对象,将`dest`作为包含 IP 地址和所需端口号的元组。如果端口是打开的,`socket.connect_ex()`返回 0,但是如果端口是关闭的,会根据被扫描的端口返回不同的数字。在端口扫描结束时,您必须使用`socket.close()`关闭套接字。 | |   | 我们将要使用的简单端口扫描仪模板如下所示,它允许我们只扫描单个端口的单个设备。如果您将它放到您的 Linux 服务器上,创建一个名为`scan_open_port.py`的新文件,并运行代码,您将能够检查单个目的地的端口状态。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano scan_open_port.py` | |   | `GNU nano 4.8` `/home/pynetauto/ssh_labs/scan_open_port.py` | |   | `import  socket` `# Import socket module` | |   | `sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)` `# Create a socket object` | |   | `dest = ("192.168.183.10", 22)` `# IP and Port to scan` | |   | `port_open = sock.connect_ex(dest)` `# Create socket connect object` | |   | `# open returns int 0, closed returns an integer based on port number` | |   | `if port_open == 0:` `# If port is opened, it returns 0` | |   | `print(port_open)` `# print result = 0` | |   | `print("On ", {dest[0]}, "port is open.")` `# Informational` | |   | `else:` | |   | `print(port_open)` `# print result, an integer other than 0` | |   | `print("On ", {dest[0]}, "port is closed.")` `# Informational` | |   | `sock.close()` `# close socket object` | |   | `^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` | |   | 当您在实验室的`LAB-R1` (192.168.183.10)路由器上运行该脚本时,结果将如下所示: | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python3 scan_open_port.py` | |   | `0` | |   | `On  {'192.168.183.10'} port is open.` | |   | 当您将端口 22 (SSH)更改为 80 (HTTP)并再次扫描端口时,它会返回 111 和“端口已关闭”消息。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python3 scan_open_port.py` | |   | `111` | |   | `On  {'192.168.183.10'} port is closed.` | | **2** | 前面的脚本工作得很好,但不能很好地适应我们的用例,我们必须修改脚本,使它在我们的环境中更具可伸缩性和通用性。使用前面的脚本作为我们的模板,让我们重写脚本来一次扫描多个设备的端口。我的脚本看起来如下,但是没有正确或错误的编码方式,所以你也可以通过修改来增加你的天赋。由于这个脚本扫描多个端口,我将其命名为`scan_open_ports.py`(末尾有复数“s”)。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano scan_open_ports.py` | |   | `GNU nano 4.8` `/home/pynetauto/ssh_labs/scan_open_ports.py` | |   | `import  socket` `# Import socket module` | |   | `ip_addresses = ["192.168.183.10", "192.168.183.20", "192.168.183.101", "192.168.183.102", "192.168.183.133"]` `# IP address list` | |   | `for ip in ip_addresses:` `# get ip from the list, ip_addresses` | |   | `for port in range (22, 24):` `# port range, ports 22-23, always n-1 for the last digit` | |   | `dest = (ip, port)` `# combine both ip and port number into one object as socket method takes 1 attribute` | |   | `try` `: # Use try except method` | |   | `with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: # s as a socket object` | |   | `sock.settimeout(3)` `#` | |   | `connection = sock.connect(dest)` `# connect to destination on specified port` | |   | `print(f"On {ip}, port {port} is open!")` `# Informational` | |   | `except` `:` | |   | `print(f"On {ip}, port {port} is closed.")` `# 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` | |   | 如图所示完成脚本后,运行脚本,如果您在网络设备上同时打开了 Telnet (22)和 SSH (23 ),结果应该类似于以下内容。您也可以为其他端口扫描任务指定不同的端口范围。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python3 scan_open_ports.py` | |   | `On 192.168.183.10, port 22 is open!` | |   | `On 192.168.183.10, port 23 is open!` | |   | `On 192.168.183.20, port 22 is open!` | |   | `On 192.168.183.20, port 23 is open!` | |   | `On 192.168.183.101, port 22 is open!` | |   | `On 192.168.183.101, port 23 is open!` | |   | `On 192.168.183.102, port 22 is open!` | |   | `On 192.168.183.102, port 23 is open!` | |   | `On 192.168.183.133, port 22 is open!` | |   | `On 192.168.183.133, port 23 is open!` | | **3** | 在两个步骤中,您已经开发了一个 Python 端口扫描器,可以用于您的工作。 | ![img/492721_1_En_14_Fige_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Fige_HTML.jpg) Warning! 注意在哪里以及如何使用这种工具;您不希望在工作场所或客户的网站上违反公司的安全政策。如果您的工作不建议在没有适当的更改控制和批准的情况下执行端口扫描任务,请不要运行端口扫描程序。 | # | 工作 | | --- | --- | |   | 好了,现在你有了另一个工具;基于前面的脚本,让我们继续编写一些代码来检查端口,如果我们的设备上启用了 Telnet,让 Python 应用 SSH 进入并修改设置;然后保存更改后的配置。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano netmiko_disable_telnet.py` | |   | `GNU nano 4.8                    /` `home/pynetauto/ssh_labs/netmiko_disable_telnet.py` | |   | `#!/usr/bin/env python3` | |   | `import re` | |   | `from netmiko import ConnectHandler` | |   | `from getpass import getpass` | |   | `import time` | |   | `import  socket` | |   | `def get_credentials():` `# Enhanced User ID and password collection tool` | |   | `#Prompts for, and returns a username and password` | |   | `global username` `# Make username as a global variable to be used throughout this script` | |   | `global password` `# Make password as a global variable to be used throughout this script` | |   | `username = input("Enter your username : ")` | |   | `password = None` | |   | `while not password:` | |   | `password = getpass()` | |   | `password_verify = getpass("Retype your password : ")` `# Verify the password is correctly typed` | |   | `if password != password_verify:` | |   | `print("Passwords do not match. Please try again.")` | |   | `password = None` | |   | `return username, password` | |   | `get_credentials()` `# Run this function first to collect the username and password` | |   | `device1 = {` `# Netmiko dictionary for device1` | |   | `'device_type': 'cisco_ios',` | |   | `'ip': '192.168.183.10',` | |   | `'username': username` `,` | |   | `'password': password,` | |   | `}` | |   | `device2 = {` `# Netmiko dictionary for device2` | |   | `'device_type': 'cisco_ios',` | |   | `'ip': '192.168.183.20',` | |   | `'username': username,` | |   | `'password': password,` | |   | `}` | |   | `device3 = {` `# Netmiko dictionary for device3` | |   | `'device_type': 'cisco_ios',` | |   | `'ip': '192.168.183.101',` | |   | `'username': username,` | |   | `'password': password,` | |   | `}` | |   | `device4 = {` `# Netmiko dictionary for device4` | |   | `'device_type': 'cisco_ios',` | |   | `'ip': '192.168.183.102',` | |   | `'username': username,` | |   | `'password': password,` | |   | `}` | |   | `device5 = {` `# Netmiko dictionary for device5` | |   | `'device_type': 'cisco_ios',` | |   | `'ip': '192.168.183.133',` | |   | `'username': username,` | |   | `'password': password,` | |   | `}` | |   | `devices = [device1, device2, device3, device4, device5]` `# List of netmiko devices` | |   | `for device in devices:` `# Loop through   devices` | |   | `ip = device.get("ip", "")` `# get value of the key "ip"` | |   | `for port in range (23, 24):` `# only port 23` | |   | `dest = (ip, port)` `# Combine ip and port number to form dest object` | |   | `try:` | |   | `with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:` `# port scanner tool` | |   | `sock.settimeout(3)` `# add 3 seconds pause to socket application` | |   | `connection = sock.connect(dest)` `#` | |   | `print(f"On {ip}, port {port} is open!")` `# Informational` | |   | `net_connect = ConnectHandler(**device)` `# create a netmiko ConnectHandler object` | |   | `show_clock = net_connect.send_command("show clock\n")` `# Send "show clock" command` | |   | `print(show_clock)` `# Display time` | |   | `config_commands = ['line vty 0 15', 'transport input ssh']` `# netmiko config_commands` | |   | `net_connect.send_config_set(config_commands)` `# send netmiko send_config_set` | |   | `output = net_connect.send_command("show run | b line vty")` `# send a show command to``check vty 0 15` | |   | `print()` `# Informational` | |   | `print('-' * 80)` `# Displayed information separator line` | |   | `print(output)` `# Informational` | |   | `print('-' * 80)` `# Displayed information separator line` | |   | `print()` `# Informational` | |   | `net_connect.disconnect()` `# close netmiko connection` | |   | `except:` | |   | `print(f"On {ip}, port {port} is closed.")` | |   | `# #``This is for saving the configuration after a successful configuration change` | |   | `# net_connect = ConnectHandler(**device) # Commented out for third run` | |   | `# write_mem = net_connect.send_command("write mem\n") # Commented out for third run` | |   | `# print()` | |   | `# print('-' * 80)` | |   | `# print(write_mem) # Commented out for third run` | |   | `# print('-' * 80)` | |   | `# print()` | |   | `# net_connect.disconnect()` | |   | `^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` | | **4** | 运行`netmiko_disable_telnet.py`脚本并更新配置以禁用 Telnet 登录。 | ![img/492721_1_En_14_Figf_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Figf_HTML.jpg) Warning! 如果您在生产中使用类似的脚本,请确保您的主要远程控制台管理协议是 SSH。您遵循了公司的策略,使用自动脚本删除 Telnet 服务。 | # | 工作 | | --- | --- | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python netmiko_disable_telnet.py` | |   | `Enter your username : pynetauto` | |   | `Password: ********` | |   | `Retype your password : ********` | |   | `On 192.168.183.10, port 23 is open!` | |   | `*09:48:17.800 UTC Tue Jan 12 2021` | |   | `--------------------------------------------------------------------------------` | |   | `line vty 0 4` | |   | `logging synchronous` | |   | `login local` | |   | `transport input ssh` | |   | `line vty 5 15` | |   | `logging synchronous` | |   | `login local` | |   | `transport input ssh` | |   | `!` | |   | `no scheduler allocate` | |   | `ntp server 192.168.183.130` | |   | `!` | |   | `end` | |   | `--------------------------------------------------------------------------------` | |   | `[... omitted for brevity]` | |   | `--------------------------------------------------------------------------------` | |   | `On 192.168.183.133, port 23 is open!` | |   | `*01:40:37.823 UTC Fri Mar 1 2002` | |   | `--------------------------------------------------------------------------------` | |   | `line vty 0 4` | |   | `exec-timeout 0 0` | |   | `logging synchronous` | |   | `login local` | |   | `transport input ssh` | |   | `line vty 5 15` | |   | `exec-timeout 0 0` | |   | `logging synchronous` | |   | `login local` | |   | `transport input ssh` | |   | `!` | |   | `!` | |   | `end` | |   | `--------------------------------------------------------------------------------` | | **5** | 第二次运行脚本进行验证。现在不允许对设备进行远程登录,我们应该会收到如下关闭消息: | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python3 netmiko_disable_telnet.py` | |   | `Enter your username : pynetauto` | |   | `Password:********` | |   | `Retype your password : ********` | |   | `On 192.168.183.10, port 23 is closed.` | |   | `On 192.168.183.20, port 23 is closed.` | |   | `On 192.168.183.101, port 23 is closed.` | |   | `On 192.168.183.102, port 23 is closed.` | |   | `On 192.168.183.133, port 23 is closed.` | | **6** | 一旦前面的任务成功完成,从相同的代码中删除`except:`行下的`#`(取消注释)。然后重新运行 Python 代码来保存所有五个设备的配置。这里,我复制了原始脚本,并取消了脚本末尾的注释行: | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `cp netmiko_disable_telnet.py netmiko_disable_telnet_uncommented.py` | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `nano netmiko_disable_telnet_uncommented.py` | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python netmiko_disable_telnet_uncommented.py` | |   | 取消注释后,最后几行代码应该如下所示: | |   | `except:` | |   | `print(f"On {ip}, port {port} is closed.")` | |   | `# This is for saving the configuration after a successful configuration change.` | |   | `net_connect = ConnectHandler(**device)` | |   | `write_mem = net_connect.send_command(“write mem\n”)` | |   | `print()` | |   | `print('-' * 80)` | |   | `print(write_mem)` | |   | `print('-' * 80)` | |   | `print()` | |   | `net_connect.disconnect()` | | **7** | 再次运行脚本以保存当前运行的配置。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python netmiko_disable_telnet_uncommented.py` | |   | `Enter your username : pynetauto` | |   | `Password:` | |   | `Retype your password :` | |   | `On 192.168.183.10, port 23 is closed.` | |   | `--------------------------------------------------------------------------------` | |   | `Building configuration...` | |   | `[OK]` | |   | `--------------------------------------------------------------------------------` | |   | `[... omitted for brevity]` | |   | `--------------------------------------------------------------------------------` | |   | `On 192.168.183.133, port 23 is closed.` | |   | `--------------------------------------------------------------------------------` | |   | `Building configuration...` | |   | `[OK]` | |   | `--------------------------------------------------------------------------------` | 您刚刚开发了一个迷你端口扫描工具,然后使用它作为开发 SSH ( `netmiko`)工具的模板来关闭所有实验室设备上的 Telnet 端口。现在,您的实验室拓扑更加安全,您只能使用 SSH 连接访问路由器和交换机进行设备管理。 ## netmiko 实验 3:配置比较 在这个`netmiko`实验中,您将使用`difflib`库开发一个快速设备配置比较工具。该脚本将借用上一个实验的部分脚本,并通过检查 SSH 连接端口 22 的开放端口状态,将端口扫描器用作预检查通信验证工具。本实验将并排比较两台相似设备的运行配置。如果你做过一段时间的网络工程师,你应该熟悉 Notepad++中的比较模块和类似的工具。您必须手动登录到每个设备,运行`show`命令,将配置作为两个独立的日志或两个文本文件删除,然后在 Notepad++上打开它们,并运行比较工具。一旦进行了比较,您必须手动找出差异,将缺失的配置应用到第二个设备。这里我们正在开发一个工具,用于通过 SSH 连接访问遗留设备;然而,对于支持 REST API 的网络设备,同样的比较可以用于通过 API 调用收集运行配置。相同的脚本可以应用于收集的数据集。另外,假设您想进一步使用这个脚本。在这种情况下,我们可以利用强大的数据模块,如`pandas`和`xlsxwriter`来读取作为数据帧的行,并只提取两种配置之间的差异,这样许多手动任务就可以自动化。 对于本实验,您需要访问`Lab-R1` (192.168.183.10)和`lab-r2` (192.168.183.20)。此外,交换机和路由器的传输设备都需要通电。您将使用 WinSCP 来访问在`ubuntu20s1` Python 服务器上收集的数据。所以如果你能提前下载并安装 WinSCP 或 FileZilla 进行文件检索,你就做好了充分的准备。见图 14-6 。 ![img/492721_1_En_14_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Fig6_HTML.jpg) 图 14-6。 SSH netmiko 实验 3,使用中的设备 成功完成实验后,您将在脚本的运行目录中创建三个文件:两个包含运行配置的文本文件和一个包含比较结果的 XML 文件。按照以下步骤完成本实验: | # | 工作 | | --- | --- | | **1** | 首先,复制上一个实验中的端口扫描工具,并进行必要的修改,以便 SSH 端口 22 的打开状态可以被该工具探测到。进行更改后,该脚本应类似于以下内容: | |   | `for device in devices:` `# Loop through netmiko devices list` | |   | `ip = device.get("ip", "")` `# Get value of the key "ip" from device dictionary` | |   | `for port in range (22, 23):` `# For only port 22` | |   | `dest = (ip, port)` `# Combine ip and port number to form dest object` | |   | `try:` | |   | `with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:` | |   | `sock.settimeout(3)` | |   | `connection = sock.connect(dest)` `# Send socket request` | |   | `print (f"on {ip}, port {port} is open!")``# Informational, pass object using format` | |   | `except:` | |   | `print (f"On {ip}, port {port} is closed. Check the connectivity to {ip} again.")` | |   | `exit()` | | **2** | 现在这里是`netmiko_compare_config.py`脚本的完整源代码。这个应用使用`getpass`模块获取密码,使用时间模块添加暂停,使用套接字模块探测开放端口,`difflib`比较配置文件,当然,`netmiko`的`ConnectHandler`通过 SSH 协议连接到两个设备。尝试在键盘上键入代码,感受一下编写 Python 代码的感觉。不可避免的是,当你在编码的时候,你不得不在你的屏幕和键盘前呆上几个小时。有关进一步的解释,请参考代码中嵌入的解释。 | |   | `GNU nano 4.8` `/home/pynetauto/ssh_labs/netmiko_compare_config.py` | |   | `#!/usr/bin/env python3` | |   | `#---------------------------------------------------# Import required modules` | |   | `import time` | |   | `import socket` | |   | `import difflib` | |   | `from getpass import getpass` | |   | `from netmiko import ConnectHandler` | |   | `#---------------------------------------------------# Borrowed from previous labs` | |   | `# Functions to collect credentials and IP addresses of devices` | |   | `def get_input(prompt=''):` | |   | `try:` | |   | `line = input(prompt)` | |   | `except NameError:` | |   | `line = input(prompt)` | |   | `return line` | |   | `def get_credentials():` | |   | `#Prompts for, and returns a username and password` | |   | `username= get_input("Enter Network Admin ID     : ")` | |   | `password = None` | |   | `while not password:` | |   | `password = getpass("Enter Network Admin PWD    : ")` | |   | `password_verify = getpass("Confirm Network Admin PWD   : ")` | |   | `if password != password_verify:` | |   | `print("Passwords do not match. Please try again.")` | |   | `password = None` | |   | `return username, password` | |   | `# For IP addresses of comparing devices` | |   | `def get_device_ip():` | |   | `#Prompts for, and returns a first_ip and second_ip` | |   | `first_ip = get_input("Enter primary device IP      : ")` | |   | `while not first_ip:` | |   | `first_ip = get_input("* Enter primary device IP     : ")` | |   | `second_ip = get_input("Enter secondary device IP    : ")` | |   | `while not second_ip:` | |   | `second_ip = get_input("* Enter secondary device IP : ")` | |   | `return first_ip, second_ip` | |   | `#--------------------------------------------------------------------------------` | |   | `# Run the functions to collect credentials and ip addresses` | |   | `print("-"*40)` | |   | `username, password = get_credentials()` | |   | `first_ip, second_ip = get_device_ip()` | |   | `print("-"*40)` | |   | `#--------------------------------------------------------------------------------` | |   | `# Netmiko device dictionaries` | |   | `device1 = {` `# Netmiko dictionary for device1` | |   | `'device_type': 'cisco_ios',` | |   | `'ip': first_ip,` | |   | `'username': username,` | |   | `'password': password,` | |   | `}` | |   | `device2 = {` `# Netmiko dictionary for device2` | |   | `'device_type': 'cisco_ios',` | |   | `'ip': second_ip` `,` | |   | `'username': username,` | |   | `'password': password,` | |   | `}` | |   | `devices = [device1, device2]` | |   | `#--------------------------------------------------------------------------------` | |   | `# Re-use port scanner as a pre-check tool for reachability verification tools.` | |   | `# If an IP is not reachable, the application will exit due to a communication problem.` | |   | `for device in devices:` `# Loop through   devices` | |   | `ip = device.get("ip", "")` `# get value of the key "ip"` | |   | `for port in range (22, 23):` `# only port 22` | |   | `dest = (ip, port)` `# Combine ip and port number to form dest object` | |   | `try:` | |   | `with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:` | |   | `sock.settimeout(3)` | |   | `connection = sock.connect(dest)` | |   | `print(f"on {ip}, port {port} is open!")` | |   | `except:` | |   | `print(f"On {ip}, port {port} is closed. Check the connectivity to {ip} again.")` | |   | `exit()` | |   | `# Prompt the user to make a decision to run the tool` | |   | `response = input(f"Make a comparison of {first_ip} and {second_ip} now? [Yes/No]")` | |   | `response = response.lower()` | |   | `if response == 'yes':` | |   | `print(f"* Now making a comparison : {first_ip} vs {second_ip}")` `# Informational` | |   | `for device in devices:` `# Loop through   devices` | |   | `ip = device.get("ip", "")` `# Get value of the key "ip"` | |   | `try:` | |   | `net_connect = ConnectHandler(**device)` `# Create netmiko connection object` | |   | `net_connect.send_command("terminal length 0\n")` | |   | `output = net_connect.send_command("show running-config\n")` `# Run show running config` | |   | `show_run_file = open(f"{ip}_show_run.txt", "w+")` `# Create a file` | |   | `show_run_file.write(output)` `# Write output to file` | |   | `show_run_file.close()` `# Close-out the file` | |   | `time.sleep(1)` | |   | `net_connect.disconnect()` `# Disconnect SSH connection` | |   | `except KeyboardInterrupt:` `# Keyboard Interrupt` | |   | `print("-"*80)` | |   | `else:` | |   | `print("You have selected No. Exiting the application.")` `# Informational` | |   | `exit()` | |   | `#--------------------------------------------------------------------------------` | |   | `# Compare the two show running config files and display it in html file format.` `# Informational` | |   | `# Prepare for comparison of the text files` | |   | `device1_run = f"./{first_ip}_show_run.txt"` `# Create device1_run object, ./ is present working folder` | |   | `device2_run = f"./{second_ip}_show_run.txt"` `# Create device2_run object` | |   | `device1_run_lines = open(device1_run).readlines()` `# Convert into strings first for comparison` | |   | `time.sleep(1)` | |   | `device2_run_lines = open(device2_run).readlines()` `# Convert into strings first for comparison` | |   | `time.sleep(1)` | |   | `# Four arguments required in HtmlDiff function` | |   | `difference = difflib.HtmlDiff(wrapcolumn=60).make_file(device1_run_lines, device2_run_lines, device1_run, device2_run)` | |   | `difference_report = open(first_ip + "_vs_" + second_ip + "_compared.html", "w")` `# Create html file to write the difference` | |   | `difference_report.write(difference)` `# Writes the differences to the difference_report` | |   | `difference_report.close()` | |   | `print("** Device configuration comparison completed. Please Check the html file to check the differences.")` | |   | `print("-"*80)` | |   | `time.sleep(1)` | |   | `^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` | | **3** | 写完前面的脚本后,使用 Python 命令在`ssh_labs`目录下运行它。运行这个交互式应用后,您应该在 Linux Python 服务器的当前工作目录中自动创建了三个文件(`pwd`)。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `python netmiko_compare_config.py` | |   | `----------------------------------------` | |   | `Enter Network Admin ID     : pynetauto` | |   | `Enter Network Admin PWD    :********` | |   | `Confirm Network Admin PWD   : ********` | |   | `Enter primary device IP       : 192.168.183.10` | |   | `Enter secondary device IP    : 192.168.183.20` | |   | `----------------------------------------` | |   | `on 192.168.183.10, port 22 is open!` | |   | `on 192.168.183.20, port 22 is open!` | |   | `Make a comparison of 192.168.183.10 and 192.168.183.20 now? [Yes/No]yes` | |   | `* Now making a comparison : 192.168.183.10 vs 192.168.183.20` | |   | `** Device configuration comparison completed. Please Check the html file to check the differences.` | |   | - | |   | 如果您遇到错误或问题,请从我的 GitHub 库下载源代码,并仔细重新检查您的代码和设置。 | |   | 源代码下载网址: [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) | |   | 在 Python 网络自动化服务器上运行`ls`命令,您应该会在当前工作目录下找到这三个文件。 | |   | `pynetauto@ubuntu20s1:~/ssh_labs$` `ls -lh 192.168*` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 3.6K Jan 12 21:45 192.168.183.10_show_run.txt` | |   | `-rw-rw-r-- 1 pynetauto pynetauto  56K Jan 12 21:46 192.168.183.10_vs_192.168.183.20_compared.html` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 3.7K Jan 12 21:46 192.168.183.20_show_run.txt` | | **4** | 现在,使用 WinSCP 或 FileZilla,使用您的凭证登录到 Ubuntu Python automation 服务器,并将文件移动到您的 Windows 主机。如果您尚未安装该软件,请在登录前在此安装。我在端口 22 上使用 SCP 协议。 | |   | URL: [`https://winscp.net/eng/download.php`](https://winscp.net/eng/download.php) | |   | Figure 14-7 shows a WinSCP example with an SCP connection (port 22) to the server. Locate the three files under `/home/pynetauto/ssh_labs/` and drag and drop them to your Windows host PC folder.![img/492721_1_En_14_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Fig7_HTML.jpg)图 14-7。Win-SCP,将比较备份和运行配置备份复制到 Windows 主机 | | **12** | Now, go to the downloaded folder and open the `.html` file to review the script’s result. Now you can compare any similar devices using this interactive tool. This tool can be modified slightly to compare the difference between firewall rules and configuration, and one practical example is to use such a tool to compare Palo Alto firewalls. Using its simple XML API call feature, you can compare the primary to standby configurations. The same script can also be used to compare the configuration before and after a change has been performed on a device. How you want to use the tool is totally up to your imagination. See Figure 14-8.![img/492721_1_En_14_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_14_Fig8_HTML.jpg)图 14-8。Web 浏览器,打开 HTML 文件进行审阅 | 大多数新的网络设备都支持 REST/XML API。然而,许多公司投资了数百万美元购买只支持 Telnet、SSH 和 SNMP 的企业网络设备,这些设备多年来仍处于有效的技术支持之下。网络工程师通常在通过 SSH 或 Telnet 连接的黑色命令行控制台前开始他们的生活。现在将您的知识扩展到网络编程。你首先要研究不同网络工程师的行为,理解他们坐在黑暗的 CLI 屏幕前盯着屏幕的决策过程。网络自动化不是让机器人或机器人或软件努力工作。它是关于理解我们作为网络工程师的行为,并看到自动化的重要性以及作为技术人员可以帮助其他人的地方。几乎所有的企业网络设备都支持 SSH,因此能够使用 Python、Python 模块和 SSH 的强大功能来自动化工程师的日常任务在这个过渡时期仍然是一个强有力的工具。在这个新的软件定义网络(SDN)和“一切皆云”的时代,更重要的是了解为我们的托管网络增加更多弹性和稳定性的真正基础,以及 Python 等软件编程如何将我们带到下一个级别。 ## 摘要 本章重点介绍了使用两个 SSH 库`paramiko`和`netmiko`的路由器和交换机的 Python 网络自动化。您已经了解了如何开发更小的工具,然后将它们合并到一个更重要的工具中,以创建能够增强您的工作和团队,并最终增强您的公司的工具。您学习了如何通过 SSH 进行配置更改,对 TFTP 服务器进行`running-config`备份,开发一个简单的套接字端口扫描工具,将其应用到您的工作中,并比较两个相似的设备来定位差异。接下来,您将学习如何安排 Python 应用在指定的时间运行,并尝试 Python SNMPv3。将向您介绍`cron`和 SNMP 探索实验室,让您更多地了解如何在工作中使用 Python 实现网络自动化。 # 十五、Python 网络自动化实验室:`cron`和 SNMPv3 最终,您希望学习 Python,这样您就可以编写代码来自动化工作中的日常任务。在编写代码和开发脚本化应用之后,您可能希望在没有人在场的情况下运行脚本。在 Windows 系统上有 Windows 任务调度器,在 Linux 上,你需要`cron`来帮助你安排你的脚本在凌晨 2 点运行,或者定期运行,直到你告诉你的 Linux 系统停止。一旦熟悉 Python 和 Linux,您就可以编写脚本并安排脚本自动运行。您可能还想进一步了解 Python 如何与 SNMP 一起工作,以便您的公司可以考虑使用内部脚本进行 SNMP 监控;您可能想了解 Python 通常如何与 SNMP 交互。本章结束时,您将能够克隆 GNS3 项目,使用`cron`在 Linux 上调度任务,并使用 SNMPv3 与路由器和交换机交互。在这个过程中,我们将借用 Python 社区成员公开共享的代码,用 Python 探索 SNMPv3,了解它能为我们做什么。本章旨在让您更多地接触不同的场景,同时将 Python 实际应用于网络自动化场景。 ![img/492721_1_En_15_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Figa_HTML.jpg) ## Cron 和 SNMPv3 实验室 你已经学完了这本书的前 14 章。您可以为特定的网络任务编写简单而实用的 Python 脚本。您将很快意识到,您需要找到一种方法,在没有实际用户交互或用户干预的情况下运行您的脚本化应用,可能需要使用任务调度程序。您可能意识到,您可能能够利用 Python 自动化的力量,并将其应用于更传统的网络概念,如 SNMP 监控;可能性是无限的。首先,在为下一章做准备时,您将学习如何制作 GNS3 项目的真实克隆。 ## 为下一个实验克隆 GNS3 项目 为了准备我们的实验,你将从第十四章中复制一个 GNS3 项目。GNS3 项目导出克隆方法帮助我们制作了一个新项目,但它破坏了原始实验室的配置文件,因此从这个意义上说,它不是 GNS3 项目的真正克隆。有时,这可能不是您想要的实验室结果。您可能希望保持原始文件的完整性,并创建一个旧 GNS3 项目的真实克隆。使用克隆的项目导出方法,原始实验室变得不可用,因为 Dynamips 文件也会随着新项目的创建而导出。创建新项目而不影响原始项目的更好方法是制作原始项目文件夹的真实副本,然后用另一个名称重新导入它。您希望将`cmllab-basic`项目作为一个完整的工作项目,但同时,您不希望从头开始创建一个新的 GNS3 项目。让我们了解一下如何实现这一目标。 | # | 工作 | | --- | --- | | **1** | 如果您仍在使用`cmllab-basic`项目,首先使用`copy running-config startup-config`命令保存所有 Cisco 设备的运行配置。保存三台路由器和两台交换机的配置后,正常关闭所有设备的电源。 | | **2** | Once all routers and switches are powered off, exit GNS3 so both GNS3 and GNS3 VM are closed completely. See Figure 15-1.![img/492721_1_En_15_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig1_HTML.jpg)图 15-1。CML lab-基本 GNS3 项目,退出 GNS3 和 GNS3 虚拟机 | | **3** | Go to the `C:\Users\brendan\GNS3\projects` folder and make a copy of the `cmllab-basic` folder so it looks like Figure 15-2.![img/492721_1_En_15_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig2_HTML.jpg)图 15-2。GNS3 项目,制作现有项目文件夹的副本 | | **4** | Go to the desktop of your Windows host PC and double-click the GNS3 icon on the desktop . Wait until both GNS3 and GNS3 VM become fully operational. See Figure 15-3.![img/492721_1_En_15_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig3_HTML.jpg)图 15-3。GNS3 开始桌面图标 | | **5** | When the project window opens, select “Open a project from disk,” open the `C:\Users\brendan\GNS3\projects\cmllab-basic – Copy` folder, select the `cmllab-basic` GNS3 project file , and then click the Open button. See Figure 15-4.![img/492721_1_En_15_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig4_HTML.jpg)图 15-4。GNS3,选择“从磁盘打开项目” | | **6** | Once the copy of the `cmllab-basic` project is fully launched, wait for the lab to open correctly and then select GNS3’s File ➤ Save project as . See Figure 15-5.![img/492721_1_En_15_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig5_HTML.jpg)图 15-5。GNS3,“项目另存为”菜单项 | | **7** | Name your project and save the new project as `cmllab-devops` . See Figure 15-6.![img/492721_1_En_15_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig6_HTML.jpg)图 15-6。GNS3,另存为新项目 | | **8** | Now, go back to the `C:\Users\brendan\GNS3\projects` folder, and you will find a new project folder called `cmllab-devops`. The temporary folder has served its purpose and has now become redundant; you can go ahead and delete the `cmllab-basic – Copy` folder permanently . See Figure 15-7.![img/492721_1_En_15_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig7_HTML.jpg)图 15-7。GNS3,删除冗余副本 | | **9** | Open the `cmllab-devops` folder, and you will find your new GNS3 project file with the correct name. Now power up all devices and check their running configurations. You have successfully made a true clone of the last GNS3 project. See Figure 15-8.![img/492721_1_En_15_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig8_HTML.jpg)图 15-8。GNS3,完全克隆的 cmllab-devops 项目 | | **10** | To prepare you for the next part of our exploration, you will now add two more switches , as shown in Figure 15-9. First, delete `PC1` (VPCS) and replace it with `Lab-sw3` with a random IP address of 192.168.183.153/24 and with `Lab-SW4` with a random IP address 192.168.183.244/24\. Power on new switches and let the CPU and memory settle down. Also shown is a Cisco CSR 1000v router to be installed on VMware for a later IOS upgrade lab; this icon is only a placeholder, so you do not have to worry about the `csr1000v` router until the next chapter.![img/492721_1_En_15_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig9_HTML.jpg)图 15-9。GNS3,添加两个新交换机 | | **11** | 在配置新交换机时,从`LAB-R1`和`lab-r2` : `ping ip 192.168.183.153 repeat counter 10000" and "ping ip 192.168.183.244 repeat counter 10000`运行扩展 ping。让 ping 在后台连续运行。`LAB-R1#` `ping ip 192.168.183.153 repeat 10000``Type escape sequence to abort.``Sending 10000, 100-byte ICMP Echos to 192.168.183.153, timeout is 2 seconds:``.....................................................................................``lab-r2#` `ping ip 192.168.183.244 repeat 10000``Type escape sequence to abort.``Sending 10000, 100-byte ICMP Echos to 192.168.183.244, timeout is 2 seconds` `:``.........................................` | | **12** | 交换机通电后,使用以下配置来配置两台交换机。确保在两台交换机上禁用 IP 路由,并配置最后一个网关。对于`LAB-sw3`,出站流量将通过`LAB-R1`,对于`Lab-SW4`,未知流量将首先通过`R1`。Lab-sw3 配置:`Switch>` `en``Switch#` `configure terminal``Switch(config)#` `hostname Lab-sw3``Lab- sw3 (config)#` `no ip routing``Lab-sw3(config)#``ip default-gateway 192.168.183.10``Lab- sw3 (config)#` `enable password cisco123``Lab-sw3(config)#` `username pynetauto privilege 15 secret cisco123``Lab-sw3(config)#` `line vty 0 15``Lab-sw3(config-line)#` `login local``Lab-sw3(config-line)#` `transport input all``Lab-sw3(config-line)#` `exit``Lab-sw3(config)#` `ip domain name pynetauto. local``Lab-sw3(config)#` `crypto key generate rsa !use 1024 bits``Lab-sw3(config)#` `interface vlan 1``Lab-sw3(config-if)#``description "Native interface"``Lab-sw3(config-if)#` `ip address 192.168.183.153 255.255.255.0``Lab-sw3(config-if)#` `no shut``Lab-sw3(config-if)#` `interface GigabitEthernet0/0``Lab-sw3(config-line)#``description``Lab-sw3(config-if)#` `end``Lab-sw3#` `copy running-config startup-config`Lab-SW4 配置:`Switch>` `en``Switch#` `configure terminal``Switch(config)#` `hostname Lab-SW4``Lab-SW4(config)#` `no ip routing``Lab-SW4(config)#``ip default-gateway 192.168.183.133``Lab-SW4(config)#` `enable password cisco123``Lab-SW4(config)#` `username pynetauto privilege 15 secret cisco123``Lab-SW4(config)#` `line vty 0 15``Lab-SW4(config-line)#` `login local``Lab-SW4(config-line)#` `transport input all``Lab-SW4(config-line)#` `exit``Lab-SW4(config)#` `ip domain name pynetauto. local``Lab-SW4(config)#``crypto key generate rsa``Lab-SW4(config)#` `interface vlan 1``Lab-SW4(config-if)#` `ip address 192.168.183.244 255.255.255.0``Lab-SW4(config-if)#` `no shut``Lab-SW4(config-if)#` `end``Lab-SW4#` `copy running-config startup-config` | | **13** | 配置完成后,您将获得新交换机的 ping 响应。要停止并退出连续(扩展)ping(或 traceroute)请求,请使用 Ctrl+^或 Ctrl+Shift+6 键。`LAB-R1#` `ping ip 192.168.183.153 repeat 10000``Type escape sequence to abort.``Sending 10000, 100-byte ICMP Echos to 192.168.183.153, timeout is 2 seconds:``......................................................................``..........................................!!!!!!!!!!!!!!!!!!!!``Success rate is 85 percent (102/115), round-trip min/avg/max = 17/25/55 ms``lab-r1#` `ping ip 192.168.183.244 repeat 10000``Type escape sequence to abort` `.``Sending 10000, 100-byte ICMP Echos to 192.168.183.244, timeout is 2 seconds:``......................................................................``.... !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!``Success rate is 88 percent (102/115), round-trip min/avg/max = 12/21/44 ms` | | ![img/492721_1_En_15_Figb_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Figb_HTML.gif)记住 Ctrl+Shift+6 键。如果您的路由器或交换机挂起或您想要中断操作,您可以使用此组合键退出操作。 | | **14** | 为了确认来自新交换机的所有未知出站目的地分别通过`LAB-R1`和`R1`路由器路由,尝试在谷歌的公共 DNS、8.8.8.8 或 8.8.4.4 上使用`clear arp`和`traceroute`。 | | ![img/492721_1_En_15_Figc_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Figc_HTML.gif)如果您在 ping Google DNS 时遇到问题,可能是您的互联网 DNS 有问题,或者您没有正确配置您的设备。另一种可能性是 Windows 的最新更新导致 GNS3 出现故障,并破坏了 GNS3 使用的环回接口。即使您无法 ping 通 DNS IP 地址,现在也不要管它,继续下一部分。 | |   | `Lab-sw3#` `clear arp``Lab-sw3#` `traceroute 8.8.8.8``Type escape sequence to abort.``Tracing the route to 8.8.8.8``VRF info: (vrf in name/id, vrf out name/id)``1 192.168.183.10 36 msec 22 msec 16 msec``2 192.168.183.2 43 msec *  *``3  *  *  *``[omitted for brevity]``30  *  *  *``Lab-SW4#` `clear arp``Lab-SW4#` `traceroute 8.8.4.4``Type escape sequence to abort.``Tracing the route to 8.8.4.4``VRF info: (vrf in name/id, vrf out name/id)``1 192.168.183.133 1020 msec 41 msec 6 msec``2 192.168.183.2 38 msec *  *``3  *  *  *``[omitted for brevity]``30  *  *  *` | 如果您已经完成了前面的任务,那么您可以做更多的实验。上一任务结束时,您的拓扑中应该还有两台 IOSvL2 交换机。让我们登录您的 Linux 服务器,掌握如何使用 Linux 任务调度器,`cron`。 ## 快速启动 Linux 调度程序 cron 在本书的前面,您已经了解了一些 Linux 基础知识,并且熟悉了如何构建一个 Linux 服务器来作为多个 IP 服务的服务器。随着您对 Linux 的了解,您将很快意识到,许多您在 Windows 操作系统上不能或不被允许做的事情突然在 Linux 系统上变得可能了。它允许您快速安装功能和服务,您的 IP 服务可以立即启动并运行。如果你是一个 Windows 的人,你会记得好老的 Windows 任务调度器;顾名思义,Windows 也有任务调度工具。 Linux 有等效的任务调度器,但是它被称为`cron`(没有 GUI)。`cron`相当于 Windows 的任务调度程序,但是它的操作更简单、更可靠、更灵活,因为它没有 GUI 的负担。Linux 的`cron`工具是 Linux 默认的任务调度工具。它可以执行 Linux 的本地命令,并设置 Python 脚本等应用以设定的时间间隔运行。熟练的 Linux 系统管理员在处理系统监控和备份任务时,会使用自动化的基于 shell 的脚本或其他编程语言做大量工作。为了帮助完成这项任务,Linux 中最常用的工具是`cron`工具,它的作用与 Windows 系统的 Windows 任务调度器相同。当你写完 Python 应用(脚本)后,你必须学会如何安排你的脚本在特定的时间和特定的周期运行重复的任务,所以掌握如何使用`cron`并正确运行安排好的任务是至关重要的。当然,如果你工作的公司有一个无底洞的 IT 预算,总有一个简单的方法,那就是购买一个开箱即用的任务调度程序,但是当他们已经雇佣了像你这样的聪明人,为什么你的公司要为这些免费和开源的工具付费呢? 在本节中,您将学习如何在 Ubuntu 和 CentOS 系统中使用`cron`安排任务。基本语法几乎相同,但是有足够的变化来区别对待它们。 ### Ubuntu 20.04 LTS 任务调度:crontab 在 Ubuntu 20.04 服务器上,`cron`被称为`crontab`。您将编写一个问候 Python 应用,并使用它通过 Ubuntu 的`crontab`学习任务调度。你需要在你的 Ubuntu 虚拟服务器上完成这些任务。您在这里学到的安排简单脚本的方法也可以用于安排更复杂的脚本。在安排任务时,脚本的难度无关紧要;创建脚本计划相对简单。 | # | 工作 | | --- | --- | | **1** | 首先,创建一个新目录来放置您的脚本,编写用于`crontab`测试的三行 Python 代码,并将其保存为`print_hello_friend.py`。您可以在脚本中更改问候语。这里我们将从`datetime`库中导入`datatime`模块,并将`print (datetime.now())`语句放在打印`hello`之前。因为我来自澳大利亚,我最喜欢的问候是“G'day mate”`pynetauto@ubuntu20s1:~$` `mkdir my_cron``pynetauto@ubuntu20s1:~$` `cd my_cron``pynetauto@ubuntu20s1:~/my_cron$` `nano print_hello_friend.py``pynetauto@ubuntu20s1:~/my_cron$` `cat print_hello_friend.py``from datetime import datetime``print(datetime.now())``print("G'day Mate!")` | | **2** | 接下来,运行一次代码,检查它是否打印出了时间和您的“hello”语句。`pynetauto@ubuntu20s1:~/my_cron$` `python3 print_hello_friend.py``2021-01-13 14:01:40.770547``G'day Mate!` | | **3** | 现在您将使用`crontab`来调度这个 Python 代码的运行。在创建时间表之前,您将学习如何快速使用`crontab`。首先,您必须选择是以标准用户还是根用户的身份运行脚本。其次,决定是以不可执行文件模式运行 Python 代码,还是将其更改为可执行文件模式,然后运行它。您已经学习了如何在 Linux 服务器上将一个不可执行的文件转换成可执行文件。如果您已经决定以非根 Linux 用户的身份运行`crontab`,那么您必须执行带有前导`sudo`命令的`crontab -e`命令,这样您就可以输入`sudo crontab –e`。另一方面,如果您已经决定作为根用户运行`crontab`,您不需要附加`sudo`命令。a.非根用户 crontab 调度程序执行`pynetauto@ubuntu20s1:~/my_cron$` `sudo crontab -e`b.root 用户 crontab 调度程序执行`pynetauto@ubuntu20s1:~/my_cron$` `crontab -e` | | **4** | 为了让`cron`正确执行您的脚本,您需要检查 Python 可执行文件的位置,并运行`which python3`或`which python3.8`命令来确认目录。此外,运行`pwd`命令来检查 Python 脚本的工作目录。`pynetauto@ubuntu20s1:~/my_cron$` `which python3.8``/usr/bin/python3.8``pynetauto@ubuntu20s1:~/my_cron$` `pwd``/home/pynetauto/my_cron` | | **5** | 接下来,使用`crontab -e`命令运行`crontab`来打开 crontab 调度程序。`pynetauto`用户是一个`sudoer`,在没有`sudo`命令的情况下运行命令。当您第一次使用`crontab -e`命令时,会弹出一条消息来选择您所选择的文本编辑器。选择最简单的或者推荐的,是 nano 文本编辑器,1。`pynetauto@ubuntu20s1:~/my_cron$` `crontab -e``no crontab for pynetauto - using an empty one``Select an editor. To change later, run 'select-editor'.``1\. /bin/nano        <---- easiest``2\. /usr/bin/vim.basic``3\. /usr/bin/vim.tiny``4\. /bin/ed``Choose 1-4 [1]:` `1` | | **6** | 选择 nano 作为`crontab`调度程序的文本编辑器后,按 Enter 键打开`crontab`调度程序文件,如下所示。快速浏览信息,并将光标移动到文件的最后一行。准确输入此处显示的内容并保存文件。稍后你会学到更多关于`cron`时间格式的知识,所以你不知道每行或每个字符是什么意思。就目前而言,你必须先用自己的双手去尝试。`[…omitted for brevity]``# For more information see the manual pages of crontab(5) and cron(8)``#``# m h dom mon dow command``* * * * * /usr/bin/python3.8 /home/pynetauto/my_cron/print_hello_friend.py >> /home/pynetauto/my_cron/cron.log` | | **7** | 接下来,等待一到两分钟。您应该看到在您的`/home/pynetauto/my_cron`目录下自动创建了一个名为`cron.log`的日志文件。`pynetauto@ubuntu20s1:~/my_cron$` `ls -lh``total 8.0K``-rw-rw-r-- 1 pynetauto pynetauto 39 Jan 13 14:09 cron.log``-rw-rw-r-- 1 pynetauto pynetauto 73 Aug 27 22:48 print_hello_friend.py`用`cat`命令检查`cron.log`文件,检查`crontab`是否正常运行。您将看到`cron`作业每分钟都在运行,并且问候记录在`cron.log`文件中。所以,现在你知道了`crontab –e`中的五星(`* * * * *`)代表每一分钟。`pynetauto@ubuntu20s1:~/my_cron$` `cat cron.log``2021-01-13 14:09:01.393031``G'day Mate!``2021-01-13 14:10:01.423129``G'day Mate!` | | **8** | 要检查您的`cron`作业,请使用`crontab –l`命令。如果您已经以用户身份登录,则可以选择指定用户。根用户下没有`cron`作业,所以用它来检查另一个用户的`cron`日程。`pynetauto@ubuntu20s1:~/my_cron$` `crontab -l``* * * * * /usr/bin/python3.8 /home/pynetauto/my_cron/print_hello_friend.py >> /home/pynetauto/my_cron/cron.log` | |   | `pynetauto@ubuntu20s1:~/my_cron$` `sudo crontab -u root -l``[sudo] password for pynetauto:**********``no crontab for root` | | **9** | 要管理另一个用户的`cron`作业,您可以运行`sudo crontab –u user_name –l`。要修改时间间隔或取消另一个用户的`cron`工作,使用`sudo crontab –u user_name –e`并进行所需的时间更改。如果用户没有任何`cron`工作,它将返回`no crontab for user_name`消息。`pynetauto@ubuntu20s1:~$` `sudo crontab -u pythonadmin -e``pynetauto@ubuntu20s1:~$` `sudo crontab -u pythonadmin -l``no crontab for pythonadmin` | | **10** | 最后,要取消(停用)当前运行的`crontab`调度,用`crontab -e`打开调度文件,在特定的`cron`任务行前添加`#`,删除该行,保存文件。控制`crontab`的`cron`任务的另一种方法是使用`service cron stop` **、** `service cron start`和`service cron restart`命令停止/启动/重启`crontab`服务。`#` `* * * * * /usr/bin/python3.8 /home/pynetauto/my_cron/print_hello_friend.py >> /home/pynetauto/my_cron/cron.log` | ![img/492721_1_En_15_Figd_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Figd_HTML.gif)在步骤 6 中,每次`cron`作业运行并执行脚本时,活动都被记录到`cron.log`文件中。但是,两个右箭头或更大的符号(`>>`)在这里是什么意思呢?如果只用一支箭呢?让我们检查一下不同之处。 `* * * * * /usr/bin/python3.8 /home/pynetauto/my_cron/print_hello_friend.py >> /home/pynetauto/my_cron/cron.log` 使用两个右箭头将日志追加到下一行。因此,以前的内容不会被覆盖。 `pynetauto@ubuntu20s1:~/my_cron$` **猫 cron.log** `...` `2021-01-13 14:39:01.318529` `G'day Mate!` `2021-01-13 14:40:01.348718` `G'day Mate!` `...` 如果这里只使用了一个右箭头,那么前面的内容将被完全覆盖,只有最后执行的内容会被记录到文件中。你自己试试,亲眼看看。 `* * * * * /usr/bin/python3.8 /home/pynetauto/my_cron/print_hello_friend.py > /home/pynetauto/my_cron/cron.log` `pynetauto@ubuntu20s1:~/my_cron$` `cat cron.log` `2021-01-13 14:44:01.467980` `G'day Mate!` ### CentOS8.1 任务调度器:crond CentOS 和 Ubuntu `cron`版本之间有一些细微的差别。您必须熟悉生产中的 Red Hat 和 Debian 衍生的 Linux OS,因为它们是企业 IT 生态系统中最常见的 Linux OS。类似于 Ubuntu 的`crontab`,CentOS 提供了一个叫做`crond`的`cron`。按照 CentOS 8.1 服务器上的说明,学习如何使用`crond`。要完成以下任务,请通过 SSH 连接登录`centos8s1`。您需要在实验拓扑中运行部分或全部路由器和交换机。见图 15-10 。 | # | 工作 | | --- | --- | | **1** | 在前面的章节中,我们介绍了如何探测一个特定的端口(端口 22);在这里,您将学习如何使用 Python 脚本从您的 Linux 服务器 ping 一个 IP 地址。让我们创建一个简单的 ICMP ping 脚本,让它 ping 我们网络中的所有 IP 地址,并将其记录到`cron.log`文件中。这一次,我们将创建一个名为`ip_addresses.txt`的外部文本文件来读取每个设备的 IP 地址,然后 ping 三次,并将活动记录到`cron.log`文件中。首先,按照以下说明创建两个文件`ip_addresses.txt`和`ping_sweep1.py`:`[pynetauto@centos8s1 ~]$` `mkdir icmp_sweeper``[pynetauto@centos8s1 ~]$` `cd icmp_ sweeper``[pynetauto@centos8s1 icmp_ sweeper]$` `touch ip_addresses.txt``[pynetauto@centos8s1 icmp_ sweeper]$` `nano ip_addresses.txt``ip_addresses.txt``192.168.183.10``192.168.183.20``192.168.183.101``192.168.183.102``192.168.183.133``192.168.183.153``192.168.183.244`创建一个`ping_ip_add.py`文件并开始编写代码。重要的是,您要自己键入这些代码,以获得足够的练习。`[pynetauto@centos8s1 icmp_ sweeper]$` `nano ping_sweep1.py``ping_sweep1.py``#!/usr/bin/python3 # shebang line to tell Linux to run this file as Python3 application``import os` `# import os module``import datetime` `# import datetime module``with open("/home/pynetauto/icmp_sweeper/ip_addresses.txt", "r") as ip_addresses:` `#with open closes file automatically, read the file from specified directory``print("-"*80)` `# Divider``print(datetime.datetime.now())` `# Print current time``for ip_add in ip_addresses:` `# Use a simple for loop through each line in a file to get ip``ip = ip_add.strip()` `# Remove any white spaces``rep = os.system('ping -c 3 ' + ip)` `# Use Linux OS to send 3 ping (ICMP) messages``if rep == 0:` `# response 0 means up``print(f"{ip} is reachable.")` `# Informational``else:` `# response is not 0, then there maybe network connectivity issue``print(f"{ip} is either offline or icmp is filtered.")` `# Informational``print("-"*80)` `# Divider``print("All tasks completed.")`前面的脚本将打印当前时间,然后 ping 每个 IP 地址三次。然后,如果它在网络上,它将打印“IP 是可达的。”否则,它将显示“IP 离线或 icmp 被过滤” | | **2** | 这一次,让 Python 脚本成为可执行文件,以便在`crond`中调度时简化代码。首先,检查用户对脚本的权限。使用`chmod +x`命令使文件可执行,并再次检查权限。运行`chmod`命令并将`x`添加到文件属性后,文件的颜色应该变成绿色。`[pynetauto@centos8s1 icmp_sweeper]$` `ls -l``total 8``-rw-rw-r--. 1 pynetauto pynetauto 110 Jan 13 15:03 ip_addresses.txt``-rw-rw-r--. 1 pynetauto pynetauto 954 Jan 13 15:02 ping_sweep1.py``[pynetauto@centos8s1 icmp_sweeper]$` `chmod +x ping_sweep1.py``[pynetauto@centos8s1 icmp_sweeper]$` `ls -l``total 8``-rw-rw-r--. 1 pynetauto pynetauto 110 Jan 13 15:03 ip_addresses.txt``-rwxrwxr-x. 1 pynetauto pynetauto 954 Jan 13 15:02 ping_sweep1.py` | | **3。** | CentOS 的`crond`与 Ubuntu 的`crontab`有两个明显的不同。第一个明显的区别是名字,同样,当你必须在 CentOS 上重新安装`cron`时,你必须使用名字`sudo yum install cronie`来安装`crond`。您可以使用`rpm -q cronie`命令在 CentOS 上检查`crond`版本。`[pynetauto@centos8s1 icmp_pinger]$ rpm -q cronie``cronie-1.5.2-4.el8.x86_64`第二个区别是`cron`任务被安排在哪里。与 Ubuntu 不同,CentOS 的`crond`直接在`/etc/crontab`文件中调度任务,如下图所示:`[pynetauto@centos8s1 icmp_sweeper]$` `cat /etc/crontab``HELL=/bin/bash``PATH=/sbin:/bin:/usr/sbin:/usr/bin``MAILTO=root``# For details see man 4 crontabs``# Example of job definition:``# .---------------- minute (0 - 59)``# |  .------------- hour (0 - 23)``# |  |  .---------- day of month (1 - 31)``# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...``# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat``# |  |  |  |  |``# *  *  *  *  * user-name  command to be executed` | | **4** | 接下来,使用`systemctl status crond`命令检查`crond`服务是否可操作。`[pynetauto@centos8s1 icmp_sweeper]$` `systemctl status crond`●t0]`Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; vendor prese>``Active: active (running) since Wed 2021-01-13 14:55:14 AEDT; 19min ago``Main PID: 1405 (crond)``Tasks: 6 (limit: 11166)``Memory: 4.4M``CGroup: /system.slice/crond.service`■t0]`[...omitted for brevity]` | | **5** | 当调度 shell 命令或 Python 脚本在 CentOS `crond`上运行时,您可以配置和调度不同格式的`cron`命令。例如,就像前面的 Ubuntu 示例一样,您可以使用完整路径方法来调度一个`cron`作业。在 CentOS 8.1 中,同样的方法也适用,如下所示:`[pynetauto@centos8s1 icmp_sweeper]$` `which python3.6``/usr/bin/python3.6``[pynetauto@centos8s1 icmp_ sweeper]$` `pwd``/home/pynetauto/icmp_pinger``[pynetauto@centos8s1 icmp_ sweeper]$` `sudo nano /etc/crontab``[... omitted for brevity]``*/5 * * * * root /usr/bin/python3.6 /home/pynetauto/icmp_sweeper/ping_sweep1.py >> /home/pynetauto/icmp_sweeper/cron.log 2>&1`在这里,我们将 Python 脚本制作成一个可执行文件,并在不指定应用位置的情况下运行它。您的 Python 脚本必须包含第一个 shebang 行(`#!/usr/bin/python3`)。另外,请注意,您必须指定一个用户来运行脚本;因为`/etc/crontab`是根用户文件,所以作为根用户运行脚本是最容易的,所以当您调度任务时,确保为`cron`任务成功运行指定了`root`用户。现在,打开`etc/crontab`,安排你的 ICMP 脚本每五分钟向你实验室中的所有路由器和交换机发送 pings。`*/5 * * * *`表示每隔五分钟。脚本将在第 0、5、10、15、20、25、30、35、40、45、50、55 分钟运行,并在第 0 或 60 分钟运行,直到您暂停或删除`cron`作业。`[pynetauto@centos8s1 icmp_pinger]$` `sudo nano /etc/crontab``[... omitted for brevity]``*/5 * * * * root /home/pynetauto/icmp_sweeper/ping_sweep1.py >> /home/pynetauto/icmp_sweeper/cron.log 2>&1` | | **6** | 最后,打开当前工作目录下的`cron.log`文件,检查通信检查任务是否每五分钟进行一次。您可能需要等待五分钟,让`cron.log`文件出现在您的工作目录中,当您查看该文件时,它将类似于以下内容:`[pynetauto@centos8s1 icmp_sweeper]$` `ls -lh``total 12K``-rw-r--r--. 1 root      root      3.2K Jan 13 15:35 cron.log``-rw-rw-r--. 1 pynetauto pynetauto  110 Jan 13 15:03 ip_addresses.txt``-rwxrwxr-x. 1 pynetauto pynetauto  886 Jan 13 15:35 ping_sweep1.py``[pynetauto@centos8s1 icmp_pinger]$` `cat cron.log``PING 192.168.183.10 (192.168.183.10) 56(84) bytes of data.``64 bytes from 192.168.183.10: icmp_seq=1 ttl=255 time=15.8 ms``64 bytes from 192.168.183.10: icmp_seq=2 ttl=255 time=15.8 ms``64 bytes from 192.168.183.10: icmp_seq=3 ttl=255 time=21.9 ms``--- 192.168.183.10 ping statistics ---``3 packets transmitted, 3 received, 0% packet loss, time 6ms``rtt min/avg/max/mdev = 15.768/17.798/21.850/2.865 ms``[... omitted for brevity]``PING 192.168.183.244 (192.168.183.244) 56(84) bytes of data.``64 bytes from 192.168.183.244: icmp_seq=2 ttl=255 time=8.33 ms``64 bytes from 192.168.183.244: icmp_seq=3 ttl=255 time=7.81 ms``--- 192.168.183.244 ping statistics ---``3 packets transmitted, 2 received, 33.3333% packet loss, time 7ms``rtt min/avg/max/mdev = 7.808/8.070/8.333/0.277 ms``--------------------------------------------------------------------------------``2021-01-13 15:35:01.973235``192.168.183.10 is reachable.``192.168.183.20 is reachable.``192.168.183.101 is reachable.``192.168.183.102 is reachable` `.``192.168.183.133 is reachable.``192.168.183.153 is reachable.``192.168.183.244 is reachable.``--------------------------------------------------------------------------------``All tasks are completed.` | | **7** | 在本实验结束时,请确保您返回到`/etc/crontab`文件,并将`#`添加到每一行的开头以禁用它。您可以选择删除该行,然后保存更改。 | ![img/492721_1_En_15_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig10_HTML.jpg) 图 15-10。 centos8s1,crond 实验室所需设备 ![img/492721_1_En_15_Fige_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fige_HTML.gif)开箱即用,`crontab`比 crond 好用多了。此外,它还可以灵活地在不同的用户账户下安排`cron`任务,因此许多用户会发现`crontab`使用起来更加直观。如果你和我一样,可以选择使用以下 Linux 命令在 CentOS8.1 服务器上安装`crontab`。但是请注意,在生产环境中,一些旧的 Linux 服务器没有连接到互联网,您可能必须使用`crond`来调度任务,因此了解不同 Linux 操作系统上不同`cron`工具之间的差异总是有好处的。无论如何,要在 CentOS8 虚拟机上安装`crontab`,请遵循以下命令: `[pynetauto@centos8s1 ~]$` `sudo dnf update` `[pynetauto@centos8s1 ~]$` `sudo dnf install crontabs` `[pynetauto@centos8s1 ~]$` `sudo systemctl enable crond.service` `[pynetauto@centos8s1 ~]$` `sudo systemctl start crond.service` Note dnf(也称为基于 RPM 发行版的下一代包管理工具)将取代 Fedora、CentOS 和 Red Hat 发行版中的旧 YUM 包管理工具。 接下来,让我们回顾一下`cron`工作定义或五个星号。 ### 通过示例了解 cron 作业定义 为了有效地使用 Linux `cron`,您必须理解每个`cron`作业行开头的五星(星号)的含义。如果你是一个现有的`cron`用户,并且了解五个星号代表什么以及它们是如何使用的,那就太好了;然后你就可以进入下一部分了。如果您是第一次接触`cron`,请阅读本部分的其余内容,以理解五个星号的含义。以下片段直接来自 CentOS 的`/etc/crontab`文件;这是您了解如何在`cron`调度命令、脚本或任务所需要知道的全部内容: ```py # Example of job definition: # .---------------- minute (0 - 59) # | .------------- hour (0 - 23) # | | .---------- day of month (1 - 31) # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat # | | | | | # * * * * * user-name command to be executed ``` 为了便于解释,所有练习将基于`crontab`而不是`crond`,所以在 LTS Ubuntu 20 中打开你的`crond`。 通常,默认的`cron`作业以五星(星号)开始。这五颗星与任务运行的频率相关,或者换句话说,与某个任务的时间和日期间隔相关。五个值之后是运行特定任务所需的命令(CMD)。这里有一个例子: | 分钟 | 小时 | 一月中的某一天 | 月 | 星期几 | 煤矿管理局 | | --- | --- | --- | --- | --- | --- | | Zero | one | Thirty-one | Twelve | * | `/home/pynetauto/ny_eve.py >> ~/cron_time.log` | 表 15-1 是之前显示的作业定义示例的列表版本。 表 15-1。 Linux crontab:时间单位 | 单位 | 可用值 | 转换 | | --- | --- | --- | | 一小时的最小值 | 0–59 分钟 | 1 小时= 60 分钟 | | 一天中的某个小时 | 0–23 小时 | 1 天= 24 小时 | | 一月中的某一天 | 1–31 天 | 1 个月= 28–31 天 | | 月 | 1-12 日或 1 月、2 月、3 月、4 月…12 月 | 一月、二月、三月、四月… | | 星期几 | 0–6 | 1 周= 7 天 | | 煤矿管理局 | 运行的命令 | 不适用的 | 根据您的 Linux 操作系统类型,每个命令也略有不同,但它们在大多数情况下几乎是相同的。如果这些值使用另一种格式,您应该参考操作系统发行商的文档,了解不同的时间单位变化。让我们研究更多的例子来理解`cron`计划的任务定义。以下示例涵盖了最常见的职务定义。如果您有不同的需求,您可以对以下示例进行修改,并在您的`cron`作业调度中使用它们。 | # | 例子 (括号中的备选方案) | 说明 | | --- | --- | --- | | one | `* * * * *` | 五个星号是默认值,它告诉 Linux 系统每分钟执行一个任务。您已经使用了五个星号来运行 Python 脚本。 | | Two | `15 03 25 03 *` | 3 月 25 日 3 点 15 分。 | | three | `15 1,13 * * *` | 在凌晨 1 点 15 分和下午 1 点 15 分 | | four | `00 07-11 * * *` | 每天上午 7 点到 11 点之间的整点 | | five | `00 07-11 * * 1-5` | 与示例 4 相同,但仅在工作日运行(1 =周一至 5 = Fri) | | six | `0 2 * * 6,0` | 仅在周六和周日凌晨 2 点 | | seven | `00 23 * * 1``(00 23 * * Mon)` | 周一晚上 11 点 | | eight | `0/5 * * * *` | 每隔 5 分钟,每隔 5 分钟 | | nine | `0/15 * * * *` | 每隔 15 分钟分钟,每隔一刻钟 | | Ten | `0/30 * * * *` | 每 30 分钟一次,每半小时一次 | | Eleven | `0 0 * 1/1 *` | 在每月第一天的午夜 | | Twelve | `0/10 * * * 0  (0,10,20,30,40,50 * * * 0)` | 每隔 10 分钟;在更新的`cron`版本上受支持每隔 10 分钟;旧版本`cron`支持 | | Thirteen | `@reboot` | 重启后 | | Fourteen | `@hourly``(0 * * * *)` | 在分钟 | | Fifteen | `@daily``(@midnight)``(0 0 * * *)` | 在 00:00(午夜) | | Sixteen | `@weekly``(0 0 * * 0)` | 每周日午夜 | | Seventeen | `@monthly``(0 0 1 * *)` | 在每月第一天的午夜 | | Eighteen | `@yearly``(@annually)``(0 0 1 1 *)` | 每年 1 月 1 日1的午夜 | ![img/492721_1_En_15_Figf_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Figf_HTML.gif)关于`cron`更详细的描述还可以在维基百科上找到。你可以通过访问下面的维基百科网站了解更多关于`cron`和如何使用`cron`的信息。 URL: [`https://en.wikipedia.org/wiki/Cron`](https://en.wikipedia.org/wiki/Cron) 此外,您可以通过使用下面列出的`cron` maker 网站,了解更多关于如何实际使用`cron`的信息: URL: [`http://www.cronmaker.com/`](http://www.cronmaker.com/) URL: [`https://crontab.guru/`](https://crontab.guru/) URL: [`http://corntab.com/`](http://corntab.com/) 在本节中,您使用 Linux `cron`配置并完成了简单 Python 脚本的调度,然后您使用示例了解了五星背后的含义。现在,你不必用心理解`cron`的定义;你可以随时回到这个页面作为参考。接下来,您将重温 SNMP 基础知识,为 Python 与网络设备上的 SNMPv3 的交互做好准备,并讨论如何通过 SNMPv3 将 Python 用于系统监控。 ## 使用 Python 运行 SNMPv3 查询 尽管 SNMP 在企业网络监控中无处不在,但它是一项未被充分重视的技术,它一直在检查我们的网络。没有多少网络工程师关注 SNMP 是如何深入工作的,或者它是做什么的,因为它无缝地工作。我们真的不能责怪工程师对 SNMP 一无所知,因为网络监控被认为是入门级的工程师工作。当你往上爬时,你会把更多的精力放在客户服务上,把它放在第一位,然后是网络管理的设计、实施和咨询部分。但是,您应该知道 Python 是如何与 SNMP 交互的吗?答案是肯定的! 在本节中,您将快速回顾 SNMP 概念,以便快速掌握,然后借用一些 Python SNMPv2c 代码来重写代码,以便使用 SNMPv3 与网络设备进行交互。使用 SNMP 开发一个完整的 Python 工具可能需要几周或几个月的时间,所以在这里您将简单地尝试学习 Python 如何使用 SNMP 与网络设备交互。 ### SNMP 快速入门指南 为了帮助您更新 SNMP 知识,我们将介绍一些基本的 SNMP 事实。如果你每天都在使用 SNMP 技术,你可以跳过这一部分,直接进入 SNMP 实验室。如果您不熟悉 SNMP,这将是 SNMP 的快速入门指南,以了解 Python 如何与 SNMP 交互。 #### 快速 SNMP 历史记录 为了更全面地了解某项技术,了解一项技术的历史总是有好处的。SNMP 最初是作为 ICMP 出现的,并不断发展,直到成为首选的监控工具。 * *1981* :互联网工程任务组(IETF)定义了互联网控制管理协议(ICMP),这是一种用于探查网络上设备的网络通信状态的协议。 * 1987 年 : ICMP 被重新定义为简单网关监控协议(SGMP),作为 RFC 1028 的一部分。 * *1989 年到 1990 年* : SGMP 被重新定义为简单网络监控协议(SNMP),它是作为 RFC 1155、1156 和 1157 的一部分引入的。 * *1990 年至今*:大多数 IT 和网络供应商接受 SNMP 作为监控 IP 设备的标准协议。 资料来源:(RFC:777,1028,1155,1156,1157) #### SNMP 标准操作 与许多服务器和客户机模型一样,SNMP 也使用服务器和客户机模型。SNMP 服务器被称为*管理器*,它与客户端(节点)一起工作,被称为*代理*。即使你已经完成了 CCNA 的研究,你可能也不熟悉标准 SNMP 操作的细节。如果这是您第一次使用 SNMP,请不要跳过这一部分。 * SNMP 充当服务器(管理器)和客户端(代理)。 * 通常,SNMP 代理作为代理安装在受监控的设备上。 * SNMP 遵循预设的规则来监控网络设备,并收集和存储监控设备的信息。 * 代理可以使用轮询或事件报告方法将其状态发送给管理器。 * 管理信息库(MIB)是代理用来收集数据的信息标准。 * 对象标识(OID)是 MIB 管理对象的主键。 #### SNMP 轮询与陷阱(事件报告)方法 为了收集数据,SNMP 管理器向代理发送 Get 请求消息,然后代理发回一条带有其状态的响应消息。SNMP 服务器请求特定的信息,然后 SNMP 客户端做出响应。在 SNMP 服务器可以更改 SNMP 客户端状态的环境中,服务器可以使用 SNMP Set 请求来更改 SNMP 客户端的状态。换句话说,你可以使用 SNMP 来控制客户端,但通常情况下,SNMP 的功能仅限于监控网络。轮询方法在网络上是健谈的,因此它比陷阱方法产生更多的流量。在陷阱方法中,只有在其系统中有任何被监视的信息时,SNMP 客户端才向 SNMP 服务器的陷阱接收器发送信息。这是当今业界最常见的网络监控。此外,在稳定的网络中,它产生的网络流量相对较少。 #### SNMP 版本 表 15-2 显示了运行中的所有 SNMP 版本。很多遗留系统并不支持更安全的 SNMPv3,所以在大多数网络中,使用最多的是俗称 SNMPv2 的 SNMPv2c,但由于安全问题,越来越多的企业正在向 SNMPv3 迁移。随着旧设备的更新,越来越多的设备可以支持 SNMPv3,并且随着旧设备的退役,SNMP v3 正在慢慢得到部署。远离 SNMPv2 的另一个重要催化剂是虚拟化和基于云的基础架构的引入,这些基础架构现在是企业网络和服务器网络的标准。在虚拟化云世界中,除了实际的服务器群和物理连接,几乎所有东西都以文件或软件的形式存在,这意味着技术不会受到硬件限制的束缚。查看表 15-2 以检查每个版本的特性。 表 15-2。 SNMP 版本 | 版本 | 特征 | | --- | --- | | SNMPv1 | 使用社区字符串(密钥);没有内置的安全性;仅支持 32 位系统 | | SNMPv2c | 最常用;支持 32/64 位系统;提高了 64 位系统的性能;与 v1 几乎相同的功能;安全性低;跨平台支持 | | SNMPv3 | 经理和代理系统已更改为对象;增加了 64 位系统安全性;保证认证和隐私;将每个 SNMP 实体检测为唯一的引擎 ID 名称 | #### 了解 TCP/IP 协议栈中的 SNMP 协议 SNMP 是 TCP/IP 协议栈中的应用协议,位于 OSI 模型的传输层;它使用 UDP 在服务器和客户端之间进行通信。以下是 SNMP 通信端口信息以及管理器和代理如何通信的快速摘要: * SNMP Get 消息通过 UDP 端口 161 进行通信。 * SNMP Set 消息也通过 UDP 端口 161 进行通信。 * 使用 SNMP 陷阱时,SNMP 陷阱通过 UDP 端口 162 进行通信,发送消息的代理不必检查陷阱是否发送正确。 * 当使用 SNMP Inform 时,除了它确认已经通过 UDP 端口 162 正确接收到 Inform 消息之外,它的行为是相同的。 #### 关于 SNMP 消息类型 SNMP 管理器和 SNMP 代理之间发送和接收的消息有六种类型;表 15-3 总结了这些信息类型。 表 15-3。 SNMP 消息类型 | 消息类型 | PDU 类型 | 说明 | | --- | --- | --- | | `get-request` | Zero | 获取代理管理 MIB 中指定的对象的值。 | | `get-next-request` | one | 获取代理管理 MIB 中指定的对象的下一个对象值。当 MIB 项目有一个表时使用它。 | | `get-bulk-request` | one | 它的工作方式类似于`get-next-request`,并按照指定的次数获取代理管理 MIB 中指定的对象值。 | | `set-request` | Two | 设置(更改)代理管理 MIB 中指定的对象的值。 | | `get-response` | three | 返回经理请求的结果。 | | `traps` | four | 当代理管理 MIB 中发生特定事件时,此消息会发送给管理器以通知管理器。 | **PDU 类型值的范围仅在 0 和 4 之间。* #### 了解 SNMP 的 SMI、MIB 和 OID 接下来,在简要解释了 SNMP 的 SMI、MIB 和 OID 的定义之后,您将了解安全方法、访问策略、陷阱的同步和异步。让我们快速了解一下 SNMP 术语以及它们在 SNMP 监控网络中的用途。 * *结构化管理信息(SMI)* : SMI 是一个定义和配置 MIB 以及创建和管理 MIB 标准的工具。 * *管理信息库(MIB)*:MIB 是从 SNMP 服务器的角度管理的一组对象。MIB 是管理员可以查看或设置的对象数据库。每个对象都有一个树状结构,您可以使用特定的 MIB 值检查代理的状态或更改其值。ASN.1 (Abstract Notation One)是一种描述对象的脚本语言,包括 SNMP 对象。 * *对象标识符(OID)* :必须分配 OID 来生成 MIB。oid 被表示为一系列唯一的特定数字,使用类似于 IP 地址的符号。OID 是一个标识符(主键),它指定了 MIB 的管理对象。您可以运行 OID 查找工具来详细了解 OID。 #### SNMP 访问策略 为了确保 SNMP 安全性,在管理器和代理通信之间使用 SNMP 社区字符串。manager 需要适当的团体字符串来访问网络中的关键设备信息。大多数网络厂商在网络设备出厂时将默认的公共社区字符串配置为 public;许多网络管理员更改社区字符串,以防止入侵者获取有关网络设置的信息。SNMP 可以根据不同的社区字符串提供不同的信息访问级别。有只读、读写和陷阱社区字符串。 * *SNMP 只读社区字符串*:代理的信息可以从其他 SNMP 管理器中读取。`InterMapper`使用只读信息映射设备。 * *SNMP 读写社区字符串*:使用请求进行通信,可以读取和更改其他设备的状态或设置。注意`InterMapper`是只读的,所以它不使用读写字符串模式。 * *SNMP 陷阱社区字符串*:这是代理用来向`InterMapper`发送 SNMP 陷阱的社区字符串,无论使用哪个 SNMP 陷阱社区字符串,它都会接收并存储信息。 #### 使用 SNMP 陷阱时的同步与异步通信 以下是 SNMP 使用的两种 SNMP 陷阱方法的简要说明。通常,如果使用 SNMP 在 SNMP 管理器(服务器)和代理(客户端)之间传递陷阱消息,SNMP 必须使用同步或异步模式。这两种模式的区别如下: * *同步模式*:使用同步模式时,发送端和接收端使用一个独立于数据的参考时钟。管理器和代理必须根据同步信号一起工作。为了执行连续数据传输,这是一种必须在发送侧和接收侧之间完成一次数据传输/接收才能传输下一次数据的模式。 * *异步模式*:在使用异步模式的情况下,时差由接收信号时钟识别,并一次发送一个字符,与发送方的时钟无关。起始位和停止位被添加到要发送的数据包的前面。换句话说,在 SNMP 代理以异步模式运行的情况下,当与 Get 请求通信时,各种数据被交换,并且不规则传输可能是可能的,而不管时间和顺序。 使用 SNMP 陷阱时,生成的消息是一般陷阱或通知陷阱消息。陷阱使用异步通信方法。SNMP 代理单方面发送不需要接收方(SNMP 管理器)确认的信息。相反,SNMP 代理在同步模式下工作,因为发送的信息必须得到接收方(即 SNMP 管理器)的确认。 #### SNMP 相关的 Python 库 为了让 Python 使用 SNMP 与其他网络设备通信,您必须首先安装正确的 SNMP 库来帮助您实现目标。在本书中,将安装`pysnmp`模块,使用 Python 与路由器和交换机进行交互。但是,根据您希望使用 Python 和 SNMP 实现的目标,您可以利用几个 SNMP Python 模块。表 15-4 描述了 Python SNMP 相关模块,供您将来参考。 表 15-4。 Python SNMP 库 | SNMP 包名称 | 功能和下载链接 | | --- | --- | | `pysnmp` | 基于 Python 的 SNMP 模块程序执行速度慢[`https://pypi.org/project/pysnmp/`](https://pypi.org/project/pysnmp/) | | `python-netsnmp` | 对`net-snmp`使用默认 Python 绑定支持非 Pythonic 接口[`http://net-snmp.sourceforge.net/wiki/index.php/Python_Bindings`](http://net-snmp.sourceforge.net/wiki/index.php/Python_Bindings) | | `snimpy` | 为 SNMP 查询开发的 Python 工具基于 PySNMP 开发程序执行速度慢[`https://snimpy.readthedocs.io/en/latest/`](https://snimpy.readthedocs.io/en/latest/) | | `easy-snmp` | 基于`net-snmp`开发的库 Python 风格的界面支持和操作程序执行速度很快[`https://easysnmp.readthedocs.io/en/latest/`](https://easysnmp.readthedocs.io/en/latest/) | | `fastsnmpy` | 使用基于`net-snmp`的异步包装器为 SNMPwalk 开发[`https://github.com/ajaysdesk/fastsnmpy`](https://github.com/ajaysdesk/fastsnmpy) | #### 了解 MIB 和 oid 并直接浏览 oid SNMP 代理与 SNMP 管理器共享大量信息。让我们熟悉一下 MIB 和 oid。如前所述,OID 是在 MIB 中指定管理对象的标识符。这意味着 OID 是 MIB 的一部分,它使用唯一的 ID 来表示每个 MIB。OID 的例子是 1 . 3 . 6 . 1 . 2 . 1 . 1 . 3 . 0;这个 OID 是一个包含系统启动后正常运行时间信息的标识符。每个 SNMP 的这些唯一编号可以通过 MIB 库找到,设备制造商通过他们的官方网站共享这些信息。如果设备连接到网络,系统和网络设备都可以使用 MIB 和唯一的 oid 通过 SNMP 交流其系统状态。在这里,您将在我们的 GNS3 实验室中使用交互式 Linux 会话在 Cisco CML 路由器和交换机上执行`snmpwalk`。稍后,您将从互联网上找到可用的 SNMPv2c Python 代码,并将其更改为支持 SNMPv3,并在我们的实验室拓扑中探测设备信息。 ### 学习在 Linux 服务器上使用 SNMPwalk 首先,您需要安装 SNMP 软件,使您的 Linux 服务器成为 SNMP 管理器。然后,根据哪些代理使用 SNMP 版本,设置密码以向管理器进行身份验证。通常,SNMP 管理器作为独立的服务器安装在 Linux 或 Windows 服务器上,并且安装和使用支持 SNMP API 的程序。最常用的 SNMP 服务器程序是网络安全管理软件产品网络性能监视器、佩斯勒 PRTG 网络监视器和 SysAid 监视器。该软件的大部分都提供了测试用的免费发布版本。有关这些计划的更多详细信息,请参考每个供应商的网站和资源。 这里,我们将在 Linux 服务器上安装 SNMP 服务器软件,在所有网络设备上配置 SNMPv3,然后在命令行执行虚拟网络设备的 MIB 的 OID。首先,在 Linux 服务器上安装 SNMP 管理器软件,如下节所述。如果要从另一台 SNMP 服务器进行监控,可以选择安装 SNMP 代理(客户端)软件。所用的 Linux 服务器是 CentOS 8.1 服务器,因此您必须登录到`centos8s1`服务器才能逐步执行任务。实验室拓扑与之前相同;您需要一台 CentOS 服务器,并且所有路由器和交换机都处于开机状态。 #### 在 CentOS8 上安装和配置 SNMP 服务器 按照以下步骤在 CentOS 8 服务器上安装 SNMP 服务,并配置用户和用户的身份验证和授权设置。你必须在你的`centos8s1`服务器中键入用粗体标记的命令。 | # | 工作 | | --- | --- | | **1** | 首先,使用这里显示的命令在 CentOS 服务器上安装`net-snmp-utils`和`net-snmp-devel`包。`net-snmp-utils`是用于`snmpwalk`的工具。`[pynetauto@centos8s1 ~]$` `sudo yum install -y net-snmp net-snmp-utils net-snmp-devel` | | **2** | 配置 SNMPv3 用户和验证密码。此处配置的信息将用于配置 SNMP 代理,在本例中为网络设备。首先,在 CentOS 服务器上启用并启动`snmpd`;然后检查运行状态。之后,在配置新的 SNMP 用户和密码之前停止该服务。`[pynetauto@centos8s1 ~]$` `sudo systemctl enable snmpd``[pynetauto@centos8s1 ~]$` `sudo systemctl start snmpd``[pynetauto@centos8s1 ~]$` `sudo  systemctl status snmpd``[pynetauto@centos8s1 ~]$` `sudo systemctl stop snmpd`在 Linux 系统上,有三种方法可以为 SNMPv3 用户配置密码,这里将讨论这三种方法。如果在用户配置步骤中未指定认证和加密方法,将自动使用 MD5 和 AES,并且访问级别将设置为只读(`-ro`)。使用其中一种方法添加 SNMP 用户和密码。A.*命令行方式*:如果您通过命令设置用户,您可以在一个命令行中同时设置用户名、认证密码和仅加密密钥。典型的命令选项如下所示:`net-snmp-config --create-snmpv3-user [-ro] [-A authpass] [-X privpass] [-a MD5|SHA] [-x DES|AES] [username]`示例 1:使用默认 MD5 和 DES 创建 SNMP 用户,但不指定私有密码:`[pynetauto@centos8s1 ~]$` `sudo net-snmp-config --create-snmpv3-user -A AUTHPass1 SNMPUser2``adding the following line to /var/lib/net-snmp/snmpd.conf:``createUser SNMPUser1 MD5 "AUTHPass1" DES ""``adding the following line to /etc/snmp/snmpd.conf:``rwuser SNMPUser1`示例 2:指定身份验证和加密以及私有密码方法的 SNMP 用户创建。以下示例使用 DES 加密配置 MD5 身份验证:`[pynetauto@centos8s1 ~]$` `sudo net-snmp-config --create-snmpv3-user -ro -A AUTHPass1 -X PRIVPass1 -a MD5 -x DES SNMPUser3``adding the following line to /var/lib/net-snmp/snmpd.conf:``createUser SNMPUser2 MD5 "AUTHPass1" DES "PRIVPass1"``adding the following line to /etc/snmp/snmpd.conf:``rouser SNMPUser2`示例 3:这是使用 SHA 身份验证和 AES 加密的 SNMP 用户创建:`[pynetauto@centos8s1 ~]$` `sudo net-snmp-config --create-snmpv3-user -ro -A AUTHPass1 -X PRIVPass1 -a SHA -x AES SNMPUser1``adding the following line to /var/lib/net-snmp/snmpd.conf:``createUser SNMPUser3 SHA "AUTHPass1" AES "PRIVPass1"``adding the following line to /etc/snmp/snmpd.conf:``rouser SNMPUser3`B.*手动配置*:您也可以自己打开 SNMP 配置文件,在文件末尾追加新的用户信息来设置 SNMP 用户。只需打开这两个文件并添加用户信息。例 4:这是一个使用 MD5 的名为`SNMPUser1`的用户账号,创建了 DES。添加最后一行和用户名,如下所示:`[pynetauto@centos8s1 ~]$` `sudo nano /var/lib/net-snmp/snmpd.conf``[...omitted for brevity]``##############################################################``#``# snmpNotifyFilterTable persistent data``#``##############################################################``engineBoots 18``oldEngineID 0x80001f8880acfa8434b71f4a5f00000000``createUser SNMPUser4 MD5 "AUTHPass1" DES "PRIVPass1"` `<<< Add this to the last line``[pynetauto@centos8s1 ~]$ sudo nano /etc/snmp/snmpd.conf``[...omitted for brevity]``###############################################################################``#` `Further Information``#``#` `See the snmpd.conf manual page, and the output of "snmpd -H".``rwuser SNMPUser2``rouser SNMPUser3``rouser SNMPUser1``rwuser SNMPUser4 <<< Add this to the last line`*交互模式*:这个交互命令在使用 MD5 和 DES 创建 SNMP 用户时很有用。`[pynetauto@centos8s1 ~]$` `sudo net-snmp-create-v3-user``[sudo] password for pynetauto:``Enter a SNMPv3 user name to create:``SNMPUser5``Enter authentication pass-phrase:``AUTHPass1``Enter encryption pass-phrase:``[press return to reuse the authentication pass-phrase]``PRIVPass1``adding the following line to /var/lib/net-snmp/snmpd.conf:``createUser SNMPUser MD5 "AUTHPass1" DES "PRIVPass1"``adding the following line to /etc/snmp/snmpd.conf:``rwuser SNMPUser5` | | **3** | 输入以下命令再次启动 SNMP 服务,并检查 snmpd 服务的状态。状态必须为“活动:活动(正在运行)”`[pynetauto@centos8s1 ~]$` `sudo systemctl start snmpd``[pynetauto@centos8s1 ~]$` `sudo systemctl status snmpd`●t0]`Loaded: loaded (/usr/lib/systemd/system/snmpd.service; enabled; vendor preset: disabled)``Active: active (running) since Wed 2021-01-13 16:23:03 AEDT; 5s ago``Main PID: 43244 (snmpd)``Tasks: 1 (limit: 11166)``Memory: 4.9M``CGroup: /system.slice/snmpd.service`ε | | **4** | 使用`SNMPwalk`命令检查 SNMP 是否在本地服务器上正常工作。使用方法 c 中创建的`SNMPUser`运行 CentOS 服务器的`snmpwalk`。`[pynetauto@centos8s1 icmp_sweeper]$` `snmpwalk -v3 -u SNMPUser5 -l authPriv -a MD5 -A AUTHPass1 -X PRIVPass1 localhost``SNMPv2-MIB::sysDescr.0 = STRING: Linux centos8s1 4.18.0-193.14.2.el8_2.x86_64 #1 SMP Sun Jul 26 03:54:29 UTC 2020 x86_64``SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10``DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (15885) 0:02:38.85``SNMPv2-MIB::sysContact.0 = STRING: Root (configure /etc/snmp/snmp.local.conf)``SNMPv2-MIB::sysName.0 = STRING: centos8s1``SNMPv2-MIB::sysLocation.0 = STRING: Unknown (edit /etc/snmp/snmpd.conf)``SNMPv2-MIB::sysORLastChange.0 = Timeticks: (0) 0:00:00.00``SNMPv2-MIB::sysORID.1 = OID: SNMP-FRAMEWORK-MIB::snmpFrameworkMIBCompliance``SNMPv2-MIB::sysORID.2 = OID: SNMP-MPD-MIB::snmpMPDCompliance``SNMPv2-MIB::sysORID.3 = OID: SNMP-USER-BASED-SM-MIB::usmMIBCompliance``SNMPv2-MIB::sysORID.4 = OID: SNMPv2-MIB::snmpMIB``SNMPv2-MIB::sysORID.5 = OID: SNMP-VIEW-BASED-ACM-MIB::vacmBasicGroup``SNMPv2-MIB::sysORID.6 = OID: TCP-MIB::tcpMIB``SNMPv2-MIB::sysORID.7 = OID: IP-MIB::ip``SNMPv2-MIB::sysORID.8 = OID: UDP-MIB::udpMIB``[... omitted for brevity]` | ![img/492721_1_En_15_Figg_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Figg_HTML.gif)以下是 Linux `snmpwalk`命令中使用的选项句柄的简要描述: snmpwalk -v3 -u SNMPUser2 -l authPriv -a MD5 -A AUTHPass1 -X PRIVPass1 127.0.0.1 -v3(版本) -u (SNMP 用户名) -l(安全级别) -a(身份验证方法) -A(通行短语) #### 使用 Python 脚本配置 Cisco 路由器和交换机以支持 SNMPv3 以下是 Cisco 设备的 SNMP 相关设置。要实现 SNMPv3 而不是 SNMPv2c,你得比 SNMPv2c 更懂 SNMP 认证。由于 SNMPv3 遗留的兼容性问题,SNMP v2c 仍然是目前使用的最流行的 SNMP 版本。旧设备没有支持 SNMP 版本 3 的软件代码,所以除非您的网络由旧设备组成,否则 SNMPv2c 将是您的选择。未来,几乎所有设备都将支持更安全的 SNMPv3。在本书中,您将改用 SNMPv3。您将编写一个快速 Python 脚本,在所有 Cisco 设备上添加 SNMP 配置。 | # | 工作 | | --- | --- | | **1** | 所有 SNMP 代理分别管理自己的(系统)信息,这些信息可以在 SNMPv3 消息中使用。对于 SNMP 代理设备,如 Cisco 路由器和交换机,Cisco 建议在配置和使用 SNMPv3 之前设置唯一的 SNMP 引擎 id。如果在配置期间没有指定 SNMP 引擎 ID,那么企业号和默认 MAC 地址的组合将生成唯一的引擎 ID。要正确地向 Cisco 设备添加 SNMPv3 配置,您必须使用这里建议的三个`snmp-server`命令。A.引擎 ID 配置命令示例如下所示:`LAB-R1(config)#` `snmp-server engineID local 19216818310`B.SNMP 组名配置命令如下所示。在这种情况下,`GROUP1`是 SNMP 组名。该命令将创建一个名为`GROUP1`的 SNMPv3 组,并带有私有密码。`LAB-R1(config)#` `snmp-server group GROUP1 v3 priv`C.使用与管理端 SNMP 用户证书相同的信息,您可以使用以下命令在 Cisco 设备上配置 SNMP 用户:`LAB-R1(config)#` `snmp-server user SNMPUser1 GROUP1 v3 auth sha AUTHPass1 priv aes 128 PRIVPass1`您可以使用`show snmp user`来检查 Cisco 设备上的 SNMP 用户信息。在下一步中,根据前面的命令,让我们编写一个 Python 脚本,将此配置一次添加到所有设备。`LAB-R1#` `show snmp user``*Jan 13 05:22:55.217: %SYS-5-CONFIG_I: Configured from console by console``User name: SNMPUser1``Engine ID: 192168183100``storage-type: nonvolatile        active``Authentication Protocol: SHA``Privacy Protocol: AES128``Group-name: GROUP1` | | **2** | 为了让本实验更有趣,我们将复制我们之前创建的 ICMP sweeper 工具,并在登录设备和配置 SNMP 之前,在我们的脚本中将它用作预通信检查工具。因此,使用这些命令复制文件,然后将`ping_sweep1.py`脚本重命名为`precheck_tool.py`:`[pynetauto@centos8s1 ~]$` `pwd``/home/pynetauto``[pynetauto@centos8s1 ~]$` `mkdir snmp_test``[pynetauto@centos8s1 ~]$` `ls icmp_sweeper``cron.log  ip_addresses.txt  ping_sweep1.py``[pynetauto@centos8s1 ~]$` `mkdir snmp_test``[pynetauto@centos8s1 ~]$` `cp ./icmp_sweeper/ping_sweep1.py ./snmp_test/precheck_tool.py``[pynetauto@centos8s1 ~]$` `cp ./icmp_sweeper/ip_addresses.txt ./snmp_test/ip_addresses.txt``[pynetauto@centos8s1 ~]$` `cd snmp_test``[pynetauto@centos8s1 snmp_test]$` `ls``ip_addresses.txt  precheck_tool.py`在这个任务结束时,您应该在`/home/pynetauto/snmp_test`目录下有两个文件。`ip_addresses.txt`文件应该已经包含了我们拓扑中所有路由器和交换机的 IP 地址。 | | **3** | 您将在 nano 文本编辑器中打开并修改`precheck_tool.py`,以便脚本的主要部分成为一个函数。这样,我们可以从另一个 Python 脚本中调用这个函数作为一个模块。按如下方式修改文件:`[pynetauto@centos8s1 snmp_test]$` `nano precheck_tool.py``GNU nano 4.8` `/home/pynetauto/snmp_test/precheck_tool.py``#!/usr/bin/python3``import os``def icmp_pinger(ip):``rep = os.system('ping -c 3 ' + ip)``if rep == 0:``print(f"{ip} is reachable.") # Print ip is reachable if the device is on the network``else:``print(f"{ip} is either offline or icmp is filtered. Exiting.")``exit() # Exit application if any ip address is unreachable.``print("-"*80)``^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`当满足`else`条件时,例如 192.168.183.10 不可达,将退出程序,如下图所示:`[pynetauto@centos8s1 snmp_test]$` `python3 snmp_config.py``PING 192.168.183.10 (192.168.183.10) 56(84) bytes of data.``--- 192.168.183.10 ping statistics ---``3 packets transmitted, 0 received, 100% packet loss, time 62ms``192.168.183.10 is either offline or icmp is filtered. Exiting` `.` | | **4** | 现在,是时候创建配置脚本`snmp_config.py`了。作为最佳实践,我们将使用每个设备的 IP 地址来指定 SNMP 引擎 ID。IP 地址中的点将被删除,整个数字将被重新用作设备的 SNMP 引擎 ID。`[pynetauto@centos8s1 snmp_test]$ nano snmp_config.py``GNU nano 4.8` `/home/pynetauto/snmp_test/snmp_config.py``#!/usr/bin/python3``import time``import paramiko``from getpass import getpass``# Custom tools``from precheck_tool import icmp_pinger``with open("/home/pynetauto/snmp_test/ip_addresses.txt", "r") as ip_addresses:``for ip in ip_addresses:``ip = ip.strip()``icmp_pinger(ip)``username = input("Enter username : ") # Ask for username``password = getpass("Enter password : " ) # Ask for password``with open("/home/pynetauto/snmp_test/ip_addresses.txt", "r") as ip_addresses:``for ip in ip_addresses:``ip = ip.strip()``eng_id = ip.replace(".", "") # Remove dot in ip address and use it as SNMP Engine ID``print ("Now logging into " + (ip))``ssh_client = paramiko.SSHClient()``ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())``ssh_client.connect(hostname=ip,username=username,password=password)``print (f"Successful connection to {ip}\n")``print ("Now completing following tasks : " + "\n")``remote_connection = ssh_client.invoke_shell()``print (f"Adding SNMP configuration to {ip}")``remote_connection.send("show clock\n")``remote_connection.send("configure terminal\n") # Add SNMP configurations``remote_connection.send(f"snmp-server engineID local {eng_id}\n")``remote_connection.send("snmp-server group GROUP1 v3 priv\n")``remote_connection.send("snmp-server user SNMPUser1 GROUP1 v3 auth sha AUTHPass1 priv aes 128 PRIVPass1\n")``remote_connection.send("do wri\n")``remote_connection.send("exit\n")``time.sleep(2)``print ()``time.sleep(2)``output = remote_connection.recv(65535)``print((output).decode('ascii'))``print(f"Successfully configured {ip} & Disconnecting.")``print("-"*80)``ssh_client.close``time.sleep(2)``print("All tasks were completed successfully. Bye!")``^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** | 在运行脚本之前,您需要通过运行下面的`pip3`命令来安装`paramiko`库。在`centos8s1`服务器上安装`paramiko`。`[pynetauto@centos8s1 snmp_test]$` `sudo pip3 install paramiko` | | **6** | 您应该看到最后一行代码是一个成功的配置脚本,它在主脚本的`for`循环之外。运行`snmp_config.py`并在所有设备上配置 SNMP 组设置。该脚本最初将使用 ICMP 检查网络连接。如果任何设备不可达,应用将退出。如果所有设备都在网络上,脚本将运行并完成所有设备上的 SNMP 配置。`[pynetauto@centos8s1 snmp_test]$` `python3 snmp_config.py``PING 192.168.183.10 (192.168.183.10) 56(84) bytes of data.``64 bytes from 192.168.183.10: icmp_seq=1 ttl=255 time=14.7 ms``64 bytes from 192.168.183.10: icmp_seq=2 ttl=255 time=11.5 ms``64 bytes from 192.168.183.10: icmp_seq=3 ttl=255 time=6.22 ms` | |   | `--- 192.168.183.10 ping statistics ---``3 packets transmitted, 3 received, 0% packet loss, time 6ms``rtt min/avg/max/mdev = 6.215/10.819/14.749/3.516 ms``192.168.183.10 is reachable` `.``--------------------------------------------------------------------------------``PING 192.168.183.20 (192.168.183.20) 56(84) bytes of data.``64 bytes from 192.168.183.20: icmp_seq=1 ttl=255 time=22.4 ms``64 bytes from 192.168.183.20: icmp_seq=2 ttl=255 time=25.0 ms``64 bytes from 192.168.183.20: icmp_seq=3 ttl=255 time=22.5 ms``[...omitted for brevity]``Successfully configured 192.168.183.153 & Disconnecting.``--------------------------------------------------------------------------------``Now logging into 192.168.183.244``Successful connection to 192.168.183.244``Now completing following tasks :``Adding SNMP configuration to 192.168.183.244``**************************************************************************``* IOSv is strictly limited to use for evaluation, demonstration and IOS  *``* education. IOSv is provided as-is and is not supported by Cisco's      *``* Technical Advisory Center. Any use or disclosure, in whole or in part, *``* of the IOSv Software or Documentation to any third party for any       *``* purposes is expressly prohibited except as otherwise authorized by     *``* Cisco in writing.                                                      *``**************************************************************************``Lab-SW4#show clock``*12:33:19.576 UTC Wed Jan 13 2021``Lab-SW4#configure terminal``Enter configuration commands, one per line.  End with CNTL/Z.``Lab-SW4(config)#snmp-server engineID local 192168183244``Lab-SW4(config)#snmp-server group GROUP1 v3 priv``Lab-SW4(config)#$User1 GROUP1 v3 auth sha AUTHPass1 priv aes 128 PRIVPass1``Lab-SW4(config)#do wri``Building configuration...``Successfully configured 192.168.183.244 & Disconnecting.``--------------------------------------------------------------------------------``All tasks were completed successfully. Bye!` | | **7** | 成功运行 SNMP 配置后,登录每台设备,确认每台设备上与 SNMP 相关的配置。图中显示了一个`Lab-SW4`配置的示例,但是所有其他设备都应该具有 SNMP 用户和组配置。 | |   | `Lab-SW4#show snmp user``User name:` `SNMPUser1``Engine ID: 192168183244``storage-type: nonvolatile        active``Authentication Protocol: SHA``Privacy Protocol: AES128``Group-name: GROUP1``Lab-SW4#show run | in snmp``snmp-server engineID local 192168183244``snmp-server group GROUP1 v3 priv` | 您的 Cisco 设备现在已准备好与您的 SNMP 服务器进行 SNMP 通信。 #### 从 Linux 服务器进行 SNMPwalk 从您的`centos8s1`服务器,让我们继续使用 SNMPv3 命令执行`snmpwalk`;这里有很多东西要学。 | # | 工作 | | --- | --- | | **1** | CentOS 8.1 服务器使用以下`snmpwalk`命令通过 SNMP 检索`LAB-R1`的 vIOS MIB 使用的 OID 信息。如果使用此命令,将加载所有 OID 信息,如下所示:`[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10``SNMPv2-MIB::sysDescr.0 = STRING: Cisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)``Technical Support:` `http://www.cisco.com/techsupport``Copyright (c) 1986-2016 by Cisco Systems, Inc.``Compiled Tue 22-Mar-16 16:19 by prod_rel_team``SNMPv2-MIB::sysObjectID.0 = OID: SNMPv2-SMI::enterprises.9.1.1041``DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (157667) 0:26:16.67``SNMPv2-MIB::sysContact.0 = STRING:``SNMPv2-MIB::sysName.0 = STRING: LAB-R1.pynetauto.local``SNMPv2-MIB::sysLocation.0 = STRING:``SNMPv2-MIB::sysServices.0 = INTEGER: 78``SNMPv2-MIB::sysORLastChange.0 = Timeticks: (0) 0:00:00.00``SNMPv2-MIB::sysORID.1 = OID: SNMPv2-SMI::enterprises.9.7.129``SNMPv2-MIB::sysORID.2 = OID: SNMPv2-SMI::enterprises.9.7.115``SNMPv2-MIB::sysORID.3 = OID: SNMPv2-SMI::enterprises.9.7.265``SNMPv2-MIB::sysORID.4 = OID: SNMPv2-SMI::enterprises.9.7.112``SNMPv2-MIB::sysORID.5 = OID: SNMPv2-SMI::enterprises.9.7.106``SNMPv2-MIB::sysORID.6 = OID: SNMPv2-SMI::enterprises.9.7.47``SNMPv2-MIB::sysORID.7 = OID: SNMPv2-SMI::enterprises.9.7.122``[... omitted for brevity]` | | **2** | 如果您在其他设备上运行相同的命令,您还应该看到每个设备的所有 MIB 信息。`[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.20``[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.101``[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.102``[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.133``[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.153``[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.244` | | **3** | 使用`snmpwalk`或`snmpget`命令练习检索`LAB-SW1`的系统信息。1.3.6.1.2.1.1.3.0 ( `sysUpTime.0`)是表示系统启动时间的 OID。您可以使用`snmpget`命令来检索和检查从 SNMP 管理器到 SNMP 代理的信息。`[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 sysUpTime.0``DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (1606783) 4:27:47.83`SNMP 代理`LAB-SW1`告诉我们系统重启后的正常运行时间是 4 小时 27 分 47 秒。 | | **4** | 使用 1.3.6.1.2.1.1.5.0 ( `sysName.0`或`sysName`)检查路由器或交换机的名称。`[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 sysName.0``SNMPv2-MIB::sysName.0 = STRING: LAB-R1.pynetauto.local``[pynetauto@centos8s1 snmp_test` `]$ snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 sysName``SNMPv2-MIB::sysName.0 = STRING: LAB-R1.pynetauto.local``[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.2.1.1.5.0``SNMPv2-MIB::sysName.0 = STRING: LAB-R1.pynetauto.local` | | **5** | 如果使用 1.3.6.1.2.1.2.2.1.7( `ifAdminStatus.1`),可以查看界面的设置状态。该信息确认管理员已经激活了该界面。`[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 ifAdminStatus``IF-MIB::ifAdminStatus.1 = INTEGER: up(1)``IF-MIB::ifAdminStatus.2 = INTEGER: up(1)``IF-MIB::ifAdminStatus.3 = INTEGER: down(2)``IF-MIB::ifAdminStatus.4 = INTEGER: down(2)``IF-MIB::ifAdminStatus.5 = INTEGER: up(1)``IF-MIB::ifAdminStatus.6 = INTEGER: up(1)``IF-MIB::ifAdminStatus.7 = INTEGER: up(1)``[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.2.1.2.2.1.7``IF-MIB::ifAdminStatus.1 = INTEGER: up(1)``IF-MIB::ifAdminStatus.2 = INTEGER: up(1)``IF-MIB::ifAdminStatus.3 = INTEGER: down(2)``IF-MIB::ifAdminStatus.4 = INTEGER: down(2)``IF-MIB::ifAdminStatus.5 = INTEGER: up(1)``IF-MIB::ifAdminStatus.6 = INTEGER: up(1)``IF-MIB::ifAdminStatus.7 = INTEGER: up(1)` | | **6** | 您可能已经注意到,第一个接口的 OID 是在 1.3.6.1.2.1.2.2.1.7 后面附加了. 1 的 ID。如果添加并执行. 1,如下所示,GigabitEthernet0/0 端口的状态显示为 up(1)。`[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.2.1.2.2.1.7.1``IF-MIB::ifAdminStatus.1 = INTEGER: up(1)` | | **7** | 同样,在末尾添加. 3 意味着千兆以太网 0/1。此端口当前被禁用,因此显示为 down(2)。`[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.2.1.2.2.1.7.3``IF-MIB::ifAdminStatus.3 = INTEGER: down(2)` | | **8** | 如果使用 OID 1.3.6.1.2.1.2.2.1.8 ( `ifOperStatus`),可以查询所有接口的运行状态。从您的服务器快速运行一个`snmpwalk`命令进行确认。`[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 ifOperStatus``IF-MIB::ifOperStatus.1 = INTEGER: up(1)``IF-MIB::ifOperStatus.2 = INTEGER: up(1)``IF-MIB::ifOperStatus.3 = INTEGER: down(2)``IF-MIB::ifOperStatus.4 = INTEGER: down(2)``IF-MIB::ifOperStatus.5 = INTEGER: up(1)``IF-MIB::ifOperStatus.6 = INTEGER: up(1)``IF-MIB::ifOperStatus.7 = INTEGER: up(1)` | | **9** | 使用`snmpget`命令查询有关接口的信息,在本例中是 GigabitEthernet0/0。`IfDescr.1`查询接口描述,`ifOperStatus.1`查询接口运行状态。可以用 2 代替数字 1 来查询下一个千兆以太网 2 接口。`[pynetauto@centos8s1 snmp_test]$` `snmpget -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 ifDescr.1  ifOperStatus.1``IF-MIB::ifDescr.1 = STRING: GigabitEthernet0/0``IF-MIB::ifOperStatus.1 = INTEGER: up(1)` | | **10** | 下面的`snmpget`命令查询 GigabitEthernet0/3 的接口状态;由于没有使用,所以标记为`down(2)`。`[pynetauto@centos8s1 snmp_test]$` `snmpget -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 ifDescr.4  ifOperStatus.4``IF-MIB::ifDescr.4 = STRING: GigabitEthernet0/3``IF-MIB::ifOperStatus.4 = INTEGER: down(2)` | | **11** | 许多 oid 可以揭示每个组件和配置的当前状态。另一个值得注意的事实是 RMON OID;它可以泄露软件版本号和引导信息。该示例显示了路由器 RMON OID。`[pynetauto@centos8s1 snmp_test]$` `snmpwalk -v3  -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1"  -x AES -X "PRIVPass1" 192.168.183.10 rmon``RMON-MIB::rmon.19.1.0 = Hex-STRING: FF C0 00 40``RMON-MIB::rmon.19.2.0 = STRING: "15.6(2)T"``RMON-MIB::rmon.19.3.0 = ""``RMON-MIB::rmon.19.4.0 = Hex-STRING: 07 E4 08 1E 02 15 16 07``RMON-MIB::rmon.19.5.0 = INTEGER: 1``RMON-MIB::rmon.19.6.0 = STRING: "flash0:/vios-adventerprisek9-m"``RMON-MIB::rmon.19.7.0 = IpAddress: 0.0.0.0``RMON-MIB::rmon.19.8.0 = INTEGER: 1``RMON-MIB::rmon.19.9.0 = INTEGER: 1``RMON-MIB::rmon.19.12.0 = IpAddress: 0.0.0.0``RMON-MIB::rmon.19.15.0 = Hex-STRING: 00``RMON-MIB::rmon.19.16.0 = Hex-STRING: 7E 00` | vIOS 中使用的 OID 信息几乎与真实的 IOS 设备相同。您可以从本实验中学到很多东西,并从前面的示例中获得有用的信息。同样,您必须自己在键盘上键入命令,并学习前面的概念。参见图 15-11 。 ![img/492721_1_En_15_Figh_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Figh_HTML.gif)对于思科网络设备,您可以使用思科功能导航器找到所需的 OID 信息。 URL: [`https://cfnng.cisco.com/mibs`](https://cfnng.cisco.com/mibs) 有关思科 MIB 的一些问答,请参考以下内容: URL: [`https://www.cisco.com/c/en/us/support/docs/ip/simple-network-management-protocol-snmp/9226-mibs-9226.html`](https://www.cisco.com/c/en/us/support/docs/ip/simple-network-management-protocol-snmp/9226-mibs-9226.html) 以下是对思科设备最有用的 MIB,供您参考: IF-MIB:接口计数器 URL: [`https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/interface/configuration/15-s/ir-15-s-book/ir-if-mibs.html`](https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/interface/configuration/15-s/ir-15-s-book/ir-if-mibs.html) IP-MIB:包含 IP 地址 IP-FORWARD-MIB:包含一个路由表 实体-MIB:包含库存信息 LLDP-MIB:包含邻居信息 ![img/492721_1_En_15_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Fig11_HTML.jpg) 图 15-11。 思科功能导航器 ### 借用一些 Python SNMP 代码的例子 正如本书所讨论的那样,学习一门编程语言,比如 Python、JavaScript 或 Perl,并不能让你成为一名可以立即开发应用的网络工程师。基于您强大的基础网络知识,您必须尽力拓宽您支持的技术。你的旅程可能会变得漫长,也许对某些人来说太慢了。幸运的是,你经常可以在网上找到合适的代码供你使用并借用。何必多此一举呢?如果你不是第一个遇到特定问题的人,那么别人已经在你之前解决了这个问题。你只需要知道如何以及在哪里寻找你要找的特定信息。(当然,我相信有些知识更靠谱,以其他形式传递更好。) 在网上,我正在寻找一些使用 SNMP 的 Python 用例,看到了一篇很好的文章,就把这个站点加入了书签,以备将来参考。当写这本书的时候,我被要求将 SNMP 和 Python 作为这本书的一部分。但是我不想花几个星期的时间去写新的代码;我想找到一个现有的 SNMP Python 脚本来展示 Python 如何在 SNMP 中使用的例子。本节将介绍的 SNMP Python 代码来自 Alessandro Maggio 的主页。感谢亚历山德罗,在他的同意下,我已经把这个剧本作为这本书的一部分。要获得代码的完整解释,请访问 Alex 的博客。 ![img/492721_1_En_15_Figi_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_15_Figi_HTML.gif)在这里找到 Alessandro Maggio 的博客和代码教程: URL: [`https://www.ictshore.com/`](https://www.ictshore.com/) URL: [`https://www.ictshore.com/sdn/python-snmp-tutorial/`](https://www.ictshore.com/sdn/python-snmp-tutorial/) #### SNMP 实验室源代码 以下代码是 SNMPv2c 的 Python 代码。SNMPv2c 使用只读社区字符串,简化了服务器端和代理端的 SNMP 配置。尽管如此,我们都知道,SNMPv2c 不如 SNMPv3 安全,所以您将快速检查代码,并进行微小的修改,以将其转换为 SNMPv3 代码。稍后,基于一个场景,我们将编写一个简单的 Python 集成脚本来与这个脚本协调工作。首先,让我们仔细看看原始的 SNMP Python 代码。解释总是以`#`或`""" """`开头。 ```py [pynetauto@centos8s1 snmp_test]$ nano quicksnmp.py GNU nano 4.8 /home/pynetauto/snmp_test/quicksnmp.py # This code uses the High-Level API module included in the pysnmp library. from pysnmp import hlapi """ The first function or get () function defines pre-requisite information to initiate and make device information requests. Generally, for such a function to work, you specifically have to provide the target IP (or DNS name), Object ID(OID), Credentials, SNMP port number (161), EngineID, or SNMP context. There is no strict requirement to parse the EngineID and SNMP context in simpler Python code, so in this example, the EngineID or SNMP context is not parsed to communicate with the Agent. All this information forms a handler, which this script enables the Server to communicate with the Agent so that the Agent can send the requested information about itself. """ def get(target, oids, credentials, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()): handler = hlapi.getCmd( engine, credentials, hlapi.UdpTransportTarget((target, port)), context, *construct_object_types(oids) ) return fetch(handler, 1)[0] # Second function needs a single argument list_of_oids, a blank list is used to collect and return object types # information. def construct_object_types(list_of_oids): object_types = [] for oid in list_of_oids: object_types.append(hlapi.ObjectType(hlapi.ObjectIdentity(oid))) return object_types # Third function requires two arguments and uses the result list to handle any errors in the script . def fetch(handler, count): result = [] for i in range(count): try: error_indication, error_status, error_index, var_binds = next(handler) if not error_indication and not error_status: items = {} for var_bind in var_binds: items[str(var_bind[0])] = cast(var_bind[1]) result.append(items) else: raise RuntimeError('Got SNMP error: {0}'.format(error_indication)) except StopIteration: break return result # The fourth function, cast() uses the information received from PySNMP to pass the values, this function # plays the role of changing the value into int or float or string type. def cast(value): try: return int(value) except (ValueError, TypeError): try: return float(value) except (ValueError, TypeError): try: return str(value) except (ValueError, TypeError): pass return value # This code is written for SNMPv2c and uses a community string named 'ICTSHORE'. hlapi.CommunityData('ICTSHORE') # If SNMPv3 is used, a user ID and two authentication keys should be used. The script (or the Server) uses # this information to communicate with the desired SNMP Agent to get device information. hlapi.UsmUserData('testuser', authKey='authenticationkey', privKey='encryptionkey', authProtocol=hlapi.usmHMACSHAAuthProtocol, privProtocol=hlapi.usmAesCfb128Protocol) # Specifies the specific OID to obtain specified information from the SNMP Agent. '1.3.6.1.2.1.1.5.0' denotes the dot IOD for a hostname . print(get('192.168.47.10', ['1.3.6.1.2.1.1.5.0'], hlapi.CommunityData('ICTSHORE'))) ^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 ``` 源代码:[`www . ictshore . com/sdn/python-SNMP-tutorial/`](https://www.ictshore.com/sdn/python-snmp-tutorial/) #### SNMPv3 Python 代码,用于查询思科设备信息 现在你要重申之前的 SNMPv2c Python 代码来支持 SNMPv3。通过研究其他人的代码,并在实验室环境中测试脚本,您可以更深入地了解 Python 如何与不同的应用交互和行为。这也是观察和学习 SNMPv2c 和 SNMPv3 实际区别的绝佳机会。 | # | 工作 | | --- | --- | | **1** | 为了让服务器(`centos8s1`)通过 SNMP 与网络设备通信,您将首先安装服务器的`pysnmp`库。使用`sudo pip3 install pysnmp`命令完成安装。`[pynetauto@centos8s1 ~]$` `sudo pip3 install pysnmp` | | **2** | 创建一个名为`quicksnmp_v3.py`的 SNMP Python 代码后,用 nano 编辑器打开文件,将`quicksnmp.py`中的所有代码行复制粘贴到`quicksnmp_v3.py`中。`[pynetauto@centos8s1 ~]$cd snmp_test``[pynetauto@centos8s1 snmp_test]$ nano quicksnmp_v3.py` | | **3** | 要将`quicksnmp.py`转换成 SNMP 版本 3 脚本,需要进行以下修改。首先,当你阅读关于 [pysnmplabs 的文档时。com](http://pysnmplabs.com) ,你会看到需要导入并使用`from pysnmp.hlapi import UsmUserData`。[`http://snmplabs.com/pysnmp/docs/api-reference.html#pysnmp.hlapi.UsmUserData`](http://snmplabs.com/pysnmp/docs/api-reference.html%2523pysnmp.hlapi.UsmUserData)A.在代码的顶部添加`from pysnmp.hlapi import UsmUserData`,如下所示:`GNU nano 4.8` `/home/pynetauto/snmp_test/quicksnmp_v3.py``from pysnmp import hlapi``from pysnmp.hlapi import UsmUserData``def get(target, oids, credentials, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):``handler = hlapi.getCmd(``engine,``credentials,``hlapi.UdpTransportTarget((target, port)),``context,``*construct_object_types(oids)``)``return fetch(handler, 1)[0]``def construct_object_types(list_of_oids):``object_types = []``for oid in list_of_oids:``object_types.append(hlapi.ObjectType(hlapi.ObjectIdentity(oid)))``return object_types``def fetch(handler, count):``result = []``for i in range(count):``try:``error_indication, error_status, error_index, var_binds = next(handler)``if not error_indication and not error_status:``items = {}``for var_bind in var_binds:``items[str(var_bind[0])] = cast(var_bind[1])``result.append(items)``else:``raise RuntimeError('Got SNMP error: {0}'.format(error_indication))``except StopIteration:``break``return result``def cast(value):``try:``return int(value)``except (ValueError, TypeError):``try:``return float(value)``except (ValueError, TypeError):``try:``return str(value)``except (ValueError, TypeError):``pass``return value``^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`B.接下来,您需要输入您的用户信息、身份验证密钥和私钥。如前面的 SNMP 用户配置所示,SNMPv3 用户`SNMPUser1`配置了 SHA 身份验证密钥和 AES128 私钥。因此,添加如下所示的代码行:`hlapi.UsmUserData('SNMPUser1', authKey="AUTHPass1", privKey="PRIVPass1", authProtocol=hlapi.usmHMACSHAAuthProtocol, privProtocol=hlapi.usmAesCfb128Protocol)`C.输入`print`语句,以便`get()`函数可以传递正确的代理 IP 信息和其他凭证变量,从而在屏幕上打印信息。`print(get('192.168.183.10', ['1.3.6.1.2.1.1.5.0'], hlapi.UsmUserData('SNMPUser1', authKey="AUTHPass1", privKey="PRIVPass1", authProtocol=hlapi.usmHMACSHAAuthProtocol, privProtocol=hlapi.usmAesCfb128Protocol)))` | | **4** | 在最后一行代码中使用`dot OID '1.3.6.1.2.1.1.5.0'`并执行前面的 Python 代码来检索和打印设备名 192.168.183.10。`[pynetauto@centos8s1 snmp_test]$` `python3 quicksnmp_v3.py``{'1.3.6.1.2.1.1.5.0': 'LAB-R1.pynetauto.local'}` | | **5** | 如果您想检索 GigabitEthernet0/0 的运行状态,您可以在最后一行添加另一条 OID 值为`'1.3.6.1.2.1.2.2.1.7.1'`的点 OID 线。当您运行更改后的脚本时,接口状态将以数字形式返回:1 表示打开,2 表示关闭。D.在`quicksnmp_v3.py`的末尾添加以下一行:`print(get('192.168.183.10', ['1.3.6.1.2.1.2.2.1.7.1'], hlapi.UsmUserData('SNMPUser1', authKey="AUTHPass1", privKey='PRIVP$``52``[pynetauto@centos8s1 snmp_test]$` `python3 quicksnmp_v3.py``{'1.3.6.1.2.1.1.5.0': 'LAB-R1.pynetauto.local'}``{'1.3.6.1.2.1.2.2.1.7.1': 1}` | | **6** | 当您增加最后一位数字并运行 GigabitEthernet 0/3 接口状态脚本时,它会返回 2。它和任何东西都没有联系。4 是接口参考号,2 表示关闭状态。`[pynetauto@centos8s1 snmp_test]$` `python3 quicksnmp_v3.py``{'1.3.6.1.2.1.1.5.0': 'LAB-R1.pynetauto.local'}``{'1.3.6.1.2.1.2.2.1.7.4': 2}` | #### 使用 Python SNMP 代码运行接口描述查询 这个实验是上一个实验的延续,您首先必须复制一个`quicksnmp_v3.py`脚本,并创建另一个脚本来完成它。以下脚本将允许您使用 SNMPv3 Python 代码对接口及其描述运行 SNMP 查询。由于这本书是一本介绍性的书,试图介绍尽可能多的 Python 和网络自动化,所以我们不会对 SNMP 深入了解,把这个任务留给 Cisco 文档。这里重要的是学习 Python 如何使用基本的 SNMP MIB 和 OID 信息与 Cisco 设备交互。 | # | 工作 | | --- | --- | | **1** | 现在,使用 Linux 的`cp`命令,复制`quicksnmp_v3.py`并创建另一个名为`quicksnmp_v3_get_bulk_auto.py`的文件。`[pynetauto@centos8s1 snmp_test]$` `cp quicksnmp_v3.py quicksnmp_v3_get_bulk_auto.py``[pynetauto@centos8s1 snmp_test]$` `nano quicksnmp_v3_get_bulk_auto.py` | | **2** | 在行尾,添加下面的代码,并运行它。您将在底部追加下一行代码。另外,请确保您使用的是 SW3 IP 地址 192.168.183.153。这段代码将对接口的名称和描述运行 SNMP 查询。`GNU nano 4.8` `/home/pynetauto/snmp_test/quicksnmp_v3_get_bulk_auto.py``[... omitted for brevity]``def cast(value):``try:``return int(value)``except (ValueError, TypeError):``try` `:``return float(value)``except (ValueError, TypeError):``try:``return str(value)``except (ValueError, TypeError):``pass``return value # existing quicksnmp_v3.py``#Append the following to the end of the code``def get_bulk(target, oids, credentials, count, start_from=0, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):``handler = hlapi.bulkCmd(``engine,``credentials,``hlapi.UdpTransportTarget((target, port)),``context,``start_from, count,``*construct_object_types(oids)``)``return fetch(handler, count)``def get_bulk_auto(target, oids, credentials, count_oid, start_from=0, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):``count = get(target, [count_oid], credentials, port, engine, context)[count_oid]``return get_bulk(target, oids, credentials, count, start_from, port, engine, context)``its = get_bulk_auto('192.168.183.153', ['1.3.6.1.2.1.2.2.1.2 ', '1.3.6.1.2.1.31.1.1.1.18'], hlapi.UsmUserData('SNMPUser1', authKey="AUTHPass1", privKey="PRIVPass1", authProtocol=hlapi.usmHMACSHAAuthProtocol, privProtocol=hlapi.usmAesCfb128Protocol), '1.3.6.1.2.1.2.1.0')``for it in its:``for k, v in it.items():``print("{0}={1}".format(k, v))``print('')``^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` | | **3** | 最后,运行`quicksnmp_v3_get_bulk_auto.py`代码,应该会得到接口名称及其描述;由于我们只为`Gi0/0`和`vlan1`配置了描述,您将会看到这些接口的名称。`[pynetauto@centos8s1 snmp_test]$` `python3 quicksnmp_v3_get_bulk_auto.py``{'1.3.6.1.2.1.1.5.0': 'Lab-sw3.pynetauto.com'}``1.3.6.1.2.1.2.2.1.2.1=GigabitEthernet0/0``1.3.6.1.2.1.31.1.1.1.18.1="1GB Main Connection"``1.3.6.1.2.1.2.2.1.2.2=GigabitEthernet0/1``1.3.6.1.2.1.31.1.1.1.18.2=``[... omitted for brevity]``1.3.6.1.2.1.2.2.1.2.16=GigabitEthernet3/3``1.3.6.1.2.1.31.1.1.1.18.16=``1.3.6.1.2.1.2.2.1.2.17=Null0``1.3.6.1.2.1.31.1.1.1.18.17=``1.3.6.1.2.1.2.2.1.2.18=Vlan1``1.3.6.1.2.1.31.1.1.1.18.18="Native interface"` | 您可以查询和了解数百个 SNMP OID 对象。如果你有一点点的空闲时间,你可以探索更多关于思科设备 MIB 的信息,尤其是与 MIB 信息相关的 CPU 和进程。这里有一个 Cisco 链接,向您展示如何使用 SNMP 查询 CPU: 网址: [` www。思科。com/c/en/us/support/docs/IP/simple-network-management-protocol-SNMP/15215-collect-CPU-util-SNMPhtml`](https://www.cisco.com/c/en/us/support/docs/ip/simple-network-management-protocol-snmp/15215-collect-cpu-util-snmp.html) ## 摘要 在本章中,您学习了如何在 Ubuntu 和 CentOS 上使用 Linux 调度程序`cron`。现在,您可以安排 Python 应用在指定时间或指定间隔运行。一旦你完成了一个应用的开发,你就可以使用`cron`来运行任务,即使是在一天中的奇数时间,你也不会失去任何一个美好的睡眠。工具`cron`是一个强大的、免费的、开源的任务调度器,只要我们可以,我们就应该依赖开源工具。您还学习了 SNMP 基础,并使用`pysnmp`模块查看了`SNMPwalk`示例;在这个过程中,我们借用了另一个开发人员的工作,这样我们就不必重新发明轮子。这可以节省你很多时间。 有了 SNMP 和 Python,我们可以做很多事情,但那将是另外一整本书。在第十六章中,你将探索更多的网络自动化场景。此外,您还将学习使用用于 Python 测试的虚拟环境`virtualenv`,以及 Ansible、pyATS 和 Docker、sendmail 和 Twilio SMS Python 网络自动化应用开发。 # 十六、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 程序。 ![img/492721_1_En_16_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Figa_HTML.jpg) ## 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 显示了本实验中使用的逻辑连接和设备。 ![img/492721_1_En_16_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig1_HTML.jpg) 图 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_subset` will be changed to `min` from `!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`](https://docs.ansible.com/ansible/latest/network/getting_started/first_playbook.html)URL: [`https://docs.ansible.com/ansible/latest/modules/ios_facts_module.html`](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`](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_subset` will be changed to `min` from `!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`](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 "", line 1, in ``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/`](https://developer.cisco.com/docs/pyats/) (pyATS 简介) 网址: [`https://developer.cisco.com/docs/pyats-getting-started/`](https://developer.cisco.com/docs/pyats-getting-started/) (pyATS 入门) ![img/492721_1_En_16_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig2_HTML.jpg) 图 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](http://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,...):ssh`OS (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,...):ssh`OS (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>>` `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)```导入`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]` | | ![img/492721_1_En_16_Figc_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Figc_HTML.gif)如果您设备的正常运行时间少于 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)````>>>``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`![img/492721_1_En_16_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig3_HTML.jpg)图 16-3。WinSCP,从 ubuntu20s1 服务器检索 device_uptime.xlsx![img/492721_1_En_16_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig4_HTML.jpg)图 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`![img/492721_1_En_16_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig5_HTML.jpg)图 16-5。设备正常运行时间示例的 matplotlib 图 | | **18** | 在本实验结束时,确保使用`deactivate`命令并停止虚拟环境。`(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$` `deactivate``pynetauto@ubuntu20s1:~/venv1/mygenie$` | 已经向您介绍了思科自己的 pyATS,以帮助您实现网络自动化之旅。您已经使用 Python 的 re 模块收集设备的系统正常运行时间数据,然后使用`pandas`模块将数据转换成 pandas 数据帧。作为最后一步,您已经使用`matplotlib`库将数据转换成一个简单的条形图。将数据转换为图形的任务称为数据可视化。 ![img/492721_1_En_16_Figd_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Figd_HTML.gif)如果您想从 Ubuntu Server 的桌面运行之前的实验,请先尝试安装 VMware Workstation 工具,以便在您的 Windows 主机 PC 和 Linux 虚拟机之间进行复制和粘贴。遵循以下 URL 中的说明,并使用“方法 2:从 VMware 主机安装” URL: [`https://websiteforstudents.com/how-to-install-vmware-guest-tools-on-ubuntu-20-04-18-04/`](https://websiteforstudents.com/how-to-install-vmware-guest-tools-on-ubuntu-20-04-18-04/) ## 使用导入的 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 。 ![img/492721_1_En_16_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig6_HTML.jpg) 图 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/`](https://hub.docker.com/) 一旦你完成了注册,并有一些空闲时间阅读,请访问 Docker Hub 页面,熟悉 Docker 的工作方式。 URL: [`https://www.docker.com/get-started`](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/`](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`](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.![img/492721_1_En_16_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig7_HTML.jpg)图 16-7。Gmail,将不太安全的应用访问切换到打开 After you have turned on this feature, your security setting should look like Figure 16-8.![img/492721_1_En_16_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig8_HTML.jpg)图 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 ``To: Python Network Automation ``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.![img/492721_1_En_16_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig9_HTML.jpg)图 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 ``To: Python Network Automation ``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.![img/492721_1_En_16_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig10_HTML.jpg)图 16-10。检查垃圾邮件文件夹中收到的测试电子邮件测试电子邮件的以下内容将与此类似:**From:没有回复`正常关闭容器实例。 | 假设现在是星期五下午 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://www.postman.com/) (邮差休息客户端) 网址: [`https://marketplace.visualstudio.com/items?itemName=humao.rest-client`](https://marketplace.visualstudio.com/items%253FitemName%253Dhumao.rest-client) (微软 Visual Studio 的 REST 客户端) ![img/492721_1_En_16_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig11_HTML.jpg) 图 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`](https://www.twilio.com/try-twilio)![img/492721_1_En_16_Fig12_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig12_HTML.jpg)图 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.![img/492721_1_En_16_Fig13_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig13_HTML.jpg)图 16-13。Twilio 项目仪表板 | | **3** | A trial number will be offered from an available number range. Choose the first number. See Figure 16-14.![img/492721_1_En_16_Fig14_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig14_HTML.jpg)图 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.![img/492721_1_En_16_Fig15_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig15_HTML.jpg)图 16-15。带有试用编号的项目仪表板 | | **5** | 要了解更多关于如何开始使用 Twilio 的信息,请访问以下网站并阅读官方文档。URL: [`https://www.twilio.com/docs/sms/quickstart/python`](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.![img/492721_1_En_16_Fig16_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig16_HTML.jpg)图 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`](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/`](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.py**`import 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.![img/492721_1_En_16_Fig17_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig17_HTML.jpg)图 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.![img/492721_1_En_16_Fig18_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_16_Fig18_HTML.jpg)图 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 代码之前,必须在流程图中记录所有任务和流程。如果你想自动化一些东西,你必须写下来,记录每一步。 ![img/492721_1_En_17_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Figa_HTML.jpg) ## 实用 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 。 ![img/492721_1_En_17_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig1_HTML.jpg) 图 17-1。 从思科软件下载网站下载 CSR 1000v IOS XE ### 在 VMware 工作站上安装 Cisco CSR 1000v 在 VMware Workstation 15 上安装 CSR 1000v 与安装之前安装的 GNS3 VM `.ova`文件没有什么不同。但是这一次,您将创建两个虚拟机,而不是一个。此外,对于本实验,您可以保持 GNS3 完全关闭,但您仍然需要打开 Python 服务器`ubuntu20s1`。一旦我们构建了虚拟路由器,我们将使用最低配置配置两台路由器,并继续进行 IOS 升级应用开发。见图 17-2 。 ![img/492721_1_En_17_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig2_HTML.jpg) 图 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.![img/492721_1_En_17_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig3_HTML.jpg)图 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.![img/492721_1_En_17_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig4_HTML.jpg)图 17-4。csr1000v-1,给出新的路由器名称 | | **3** | Leave the Deployment Options setting as Small and click the Next button. See Figure 17-5.![img/492721_1_En_17_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig5_HTML.jpg)图 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.![img/492721_1_En_17_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig6_HTML.jpg)图 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.![img/492721_1_En_17_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig7_HTML.jpg)图 17-7。csr1000v-1,2。特征 | |   | 三个。附加配置属性,填写启用密码和测试域名。见图 17-8 。 | |   | No configuration is required for 4\. Intercloud Configuration Properties; leave the settings at the defaults.![img/492721_1_En_17_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig8_HTML.jpg)图 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.![img/492721_1_En_17_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig9_HTML.jpg)图 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.![img/492721_1_En_17_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig10_HTML.jpg)图 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.![img/492721_1_En_17_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig11_HTML.jpg)图 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.![img/492721_1_En_17_Fig12_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig12_HTML.jpg)图 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.![img/492721_1_En_17_Fig13_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig13_HTML.jpg)图 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 升级工作流程。 ![img/492721_1_En_17_Fig14_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig14_HTML.jpg) 图 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 翻译成可自动化的任务。 ![img/492721_1_En_17_Fig15_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_17_Fig15_HTML.jpg) 图 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 检查工具、用于更改应用流的用户输入工具以及带有后检查工具的路由器重新加载工具。在本章中,您还将测试这些工具并验证它们的功能。 ![img/492721_1_En_18_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Figa_HTML.jpg) ## 思科 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 升级应用中。 ![img/492721_1_En_18_Figb_HTML.gif](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Figb_HTML.gif)请注意,在现实世界中没有人会为你写这段代码,所以你必须习惯于准确快捷地自己写每一行代码。与本书中的其他代码一样,对重要代码行的解释出现在每一行的`#`标记之后。 | # | 工作 | | --- | --- | | **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.![img/492721_1_En_18_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Fig1_HTML.jpg)图 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.![img/492721_1_En_18_Fig2_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Fig2_HTML.jpg)图 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.![img/492721_1_En_18_Fig3_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Fig3_HTML.jpg)图 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`![img/492721_1_En_18_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Fig4_HTML.jpg)图 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\))` | **Regular expression** | **Explanation** | | `\d+` | This means any number more than one number. | | `(?=)` | This is a forward-looking regular expression. | | `\sbytes\sfree\)` | We are looking for an actual string, but we only use it to search. We match the string in front of this string. `\s` means blank. | | |   | 所以,前面的正则表达式应该匹配出现在`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 ` | |   | `*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 ` | |   | `*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 ` | |   | `*Jan 15 03:25:34.918: SCP: [22 -> 192.168.183.132:46546] send ` | |   | `*Jan 15 03:25:34.922: SCP: [22 <- 192.168.183.132:46546] recv ` | |   | `*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 ` | |   | `*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 ` | |   | `*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 ` | |   | `*Jan 15 03:30:10.935: SCP: [22 -> 192.168.183.132:36148] send ` | |   | `*Jan 15 03:30:10.937: SCP: [22 <- 192.168.183.132:36148] recv ` | |   | `*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.![img/492721_1_En_18_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Fig5_HTML.jpg)图 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 ` | |   | `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.![img/492721_1_En_18_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Fig6_HTML.jpg)图 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)#`![img/492721_1_En_18_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Fig7_HTML.jpg)图 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.![img/492721_1_En_18_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Fig8_HTML.jpg)图 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.![img/492721_1_En_18_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_18_Fig9_HTML.jpg)图 18-9。复制和打开 HTML 文件检查示例 | ## 摘要 在本章中,您开发了十种迷你工具,为下一章的最终 Cisco IOS 升级应用做好了准备。本章中开发的每个工具都有其使用案例,也可以作为一个独立的工具,只需稍加修改或不需要修改就可以用于生产。正如您所看到的,我们使用了各种 Python 模块来开发我们自己的 Python 应用,以满足我们的需求。这些工具可以组合成一个应用;我们可以提出各种工作流,开发不同的 Python 网络应用(工具)。编写 Python 程序的一般过程在所有厂商的网络设备上都是一样的。只要 Python 模块支持特定的供应商产品,您就可以对任何供应商产品应用类似的自动化应用开发策略。现在,让我们结合所有十个应用,创建一个功能齐全的 Cisco IOS XE 升级工具。 # 十九、Python 网络自动化实验室:组合和完成 Cisco IOS 升级应用 在这最后一章,你将把我们在第十八章开发的 10 个 Python 独立应用放在一起。该应用将是一个功能齐全的 Cisco IOS/IOS-XE 升级应用,包括预检查、IOS 上传、预重新加载检查、预重新加载配置备份、重新加载和升级后验证检查。多年来,我们一直在思科网络设备上手动升级 IOS,但现在您可以开发和组合各种 Python 工具,让您的 Python 代码为您服务。 一旦你读完这一章,你将学会如何把更小的应用组合成一个复杂的、可投入生产的应用。Python 允许程序员自由地编写几乎任何他们喜欢的代码,唯一的限制是程序员的想象力。另一方面,像 Ansible 和 Puppet 这样的配置和编排工具为非编程工程师提供了幂等特性,使他们能够非常灵活地配置和管理网络设备。但是,您仍然必须在生产设定的框架内工作。在完成本章以及本书之后,您将到达 Python 网络编程之旅的第一个里程碑。 ![img/492721_1_En_19_Figa_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_19_Figa_HTML.jpg) 在前一章中,您开发了十个与 Cisco IOS 升级相关的应用。现在是时候将这些工具结合起来,制作一个功能齐全的 Cisco IOS XE 升级工具了。随着您从事更多的 Python 项目,您总会发现一些以前没有遇到过的新东西。最好的学习方法之一是通过充满未知和挑战的项目工作。然而,如果您正在开始您的 Python 网络自动化之旅,那么尝试找到一个较小的任务来集中您的努力,并记录您可以完成的任务。开始自动化更简单的任务,然后花时间研究和学习书籍,参加在线课程,在谷歌上搜索信息,或者向已经做过类似事情的人寻求建议。 在前一章的开始,我简要地向你介绍了面向对象编程(OOP)。我们不会在最终的 IOS XE 升级应用中使用它,因为它增加了更多的复杂性。即使 OOP 是一个必须理解的概念,我们仍然可以在不创建自定义 OOP 类的情况下编写代码。在 Python 中,几乎所有东西都可以被认为是对象,即使你没有意识到 OOP,你仍然在使用它。请注意,我已经有目的地删除了最终脚本的 OOP 部分,它将不会是我们最终 Python 应用的一部分。 你已经完成了最后的练习,这是本书真正的高潮。我想祝贺你为理解本书所有章节所做的巨大努力。几年前,当我用 Python 开始我的第一次网络自动化之旅时,它仍然被认为是一种相对较新的技术趋势,并且感觉自动化任务(如在企业路由器和交换机上升级 Cisco IOS)是一个遥远的梦想。网络自动化最近获得了发展势头,现在网络自动化和 Python 编码是每个 IT/网络团队都在考虑的问题。所以事不宜迟,让我们继续完成最终的应用:一个工作的 IOS 升级工具。 在开始本节的最终任务之前,请查看第十七章中的图 17-14 和图 17-15 。您可能需要快速复习一下,以判断我们将在最后一个实验中完成什么。最终脚本的工作流程将与图 17-15 中描述的自动化任务几乎相同。 ## 创建单个 Python 文件 为了简单起见,我们将只创建一个 Python 文件(`upgrade_crs1000v.py`)。在真实的生产环境中,最佳实践是创建工具并将其保存到较小的文件(模块)中,以减少写入主脚本的行数。但是,在这里我们还在探索和学习,所以更重要的是看到每个应用如何工作的全貌。在你读完这本书之后,你可以把代码的一些部分移到单独的模块中,这在前一章中已经讨论过了。如前所述,我们将使这个应用成为一个交互式工具,在脚本运行时获取用户凭证和用户输入。设备信息将被写入一个`.csv`文件,并使用`pandas`模块作为`df`(数据帧)从该文件中读取;与让用户输入数据相比,这是一种更方便的读取大型文本数据的方式。 | # | 工作 | | --- | --- | | **1** | 您将继续执行`/home/pynetauto/my_tools/`目录中的任务;这个目录包含了我们在第十八章中开发的所有迷你 Python 应用。转到`my_tools`目录,然后在这个目录下创建`upgrade_crs1000v.py`文件。`pynetauto@ubuntu20s1:~$` `pwd` | |   | `/home/pynetauto` | |   | `pynetauto@ubuntu20s1:~$` `cd my_tools` | |   | `pynetauto@ubuntu20s1:~/my_tools$` `touch upgrade_crs1000v.py` | |   | `pynetauto@ubuntu20s1:~/my_tools$` `ls` | |   | `upgrade_crs1000v.py` | |   | 我们将很快开始填写最终的`upgrade_crs1000v.py`脚本;暂时把它留在那里作为占位符。 | | **2** | 让我们快速讨论一下我们的应用开发将引用哪些文件。如果您详细地遵循了第十八章并完成了所有的开发任务,您将会记得我们已经开发的十个应用和每个相应工具的目录名。接下来列出了您将引用的文件的名称。如果您喜欢在 Windows 文本编辑器或 IDE 中工作,可以将列出的文件保存到 Windows 文件夹中。我已经将文件复制到了`/home/pynetauto/my_tools/`目录以供参考,但是为了使完整的 IOS 应用工作,您将需要在`/home/pynetauto/my_tools/`目录中的`upgrade_crs1000v.py`和`devices_info.csv`文件,以及在`/home/pynetauto/my_tools/new_ios/`目录中的一个新的 IOS 文件。如果您已经完成了 IOS 上传部分的开发任务,那么新的 IOS 文件已经在这个目录中了。如果您尚未上传新的 IOS 文件(`csr1000v-universalk9.16.09.06.SPA.bin`),请参考 IOS 上传部分并立即完成该任务。 | |   | `pynetauto@ubuntu20s1:~/my_tools$` `ls -lh` | |   | `total 52K` | |   | `# Main script and files` | |   | `-rw-rw-r-- 1 pynetauto pynetauto    0 Jan 15 22:03` `upgrade_crs1000v.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto  271 Jan 15 23:13` `devices_info.csv` | |   | `# Python application scripts referenced:` | |   | `-rw-rw-r-- 1 pynetauto pynetauto  955 Jan 15 23:14 check_flash2.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 1.5K Jan 15 23:13 get_cred2.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 1.8K Jan 15 23:18 make_backup1.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 1.3K Jan 15 23:14 md5_validate3.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 2.4K Jan 15 23:18 md5_verify2.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto  681 Jan 15 23:14 ping_tool6.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 1009 Jan 15 23:14 ping_tool6_tools.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 6.1K Jan 15 23:18 post_check2.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 1.3K Jan 15 23:13 read_info8.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 3.9K Jan 15 23:18 reload_yesno2.py` | |   | `-rw-rw-r-- 1 pynetauto pynetauto 3.2K Jan 15 23:14 upload_ios1.py` | |   | `# new IOS .bin file location` | |   | `pynetauto@ubuntu20s1:~/my_tools$` `ls /home/pynetauto/my_tools/new_ios` | |   | `csr1000v-universalk9.16.09.06.SPA.bin` | | **3** | 如果您没有上一章的`devices_info.csv`文件,您现在可以创建它。您可以使用 Microsoft Excel 创建该文件,也可以直接在文本编辑器中使用逗号分隔值(用逗号分隔值,不使用空格)来创建该文件。创建文件后,它看起来应该如下所示: | |   | `pynetauto@ubuntu20s1:~/my_tools$` `nano 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` | | **4** | 既然标准过程已经完成,让我们开始最后的应用开发。现在,您将以线性方式将这一系列脚本添加到`upgrade_crs1000v.py`脚本中,一旦所有内容都串联起来,它将作为一个应用工作,可以直接在大多数生产环境中使用。 | |   | 首先,您将添加将在我们的应用中使用的所有 Python 模块,然后一次添加和修改一个脚本。按照这些说明,首先创建并测试一个文件应用,它将从一端运行到另一端。 | |   | `pynetauto@ubuntu20s1:~/my_tools$` `nano upgrade_crs1000v.py` | |   | 添加所有使用的模块,如下所示: | |   | `upgrade_crs1000v.py` | |   | `import socket` `# For socket networking` | |   | `import os` `# For Python Server OS` | |   | `import time` `# Python time module` | |   | `import pandas as pd` `# Pandas to read data into a data frame` | |   | `import re` `# Python regular expression module` | |   | `from getpass import getpass` `# For password collection` | |   | `import os.path` `# For Python Server OS directory` | |   | `import hashlib` `# For MD5 checks` | |   | `from netmiko import ConnectHandler, SCPConn` `# For netmiko's SSH connection and SCP file transfer` | |   | `from netmiko.ssh_exception import  NetMikoTimeoutException` `# To accept netmiko timeout exceptions` | |   | `import difflib` `# For analyzing two files and differences` | |   | `t = time.mktime(time.localtime())` `# Timer start to measure script running time` | |   | 在下一步中,您将追加和修改每个工具的代码部分,并慢慢构建主应用脚本。 | | **5** | A.用`get_cred2.py` ( `tool2_login`)把它变成一个函数。该脚本将以交互方式获取网络管理员 ID 和密码,提示启用密码,并让用户选择是或否。我们还使用正则表达式来控制用户的用户名和密码输入。对于`p1`,期望的字符长度为 5 到 30 个字符,以字母开始,以字母或数字结束。对于`p2`,期望的字符长度从 8 到 50 个字符,以字母开始,以任意字符结束。正则表达式对一些人来说可能是一个困难的话题;如果你需要帮助,你应该复习第九章。 | |   | `"""` | |   | `Step 1\. From get_cred2.py (tool2_login) application` `:` | |   | `Get network administrator ID and password, also prompt for enable secret and give the user option to select yes or no.` | |   | `p1: Between 5-30 characters long, starting with an alphabet & finishes with an alphabet or a number` | |   | `p2: Between 8 to 50 characters long, starting with an alphabet & finishes with any characters` | |   | `"""` | |   | `def get_secret(p2):` | |   | `global secret` | |   | `resp = input("Is secret same as password? (y/n) : ")` | |   | `print("-"*80)` | |   | `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):` | |   | `secret = getpass(r"*Enter the secret : ")` | |   | `secret_verify = getpass("Confirm the secret : ")` | |   | `if secret != secret_verify:` | |   | `print("!!! the secret does not match. Please try again.")` | |   | `print("-"*80)` | |   | `secret = None` | |   | `else:` | |   | `get_secret(p2)` | |   | `def get_credentials():` | |   | `p1 = re.compile(r'^[a-zA-Z][a-zA-Z0-9_-]{3,28}[a-zA-Z0-9]$')` | |   | `p2 = re.compile(r'^[a-zA-Z].{7,49}')` | |   | `global uid` | |   | `uid = input("Enter Network Admin ID : ")` | |   | `while not p1.match(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):` | |   | `pwd = getpass(r"*Enter Network Admin PWD : ")` | |   | `pwd_verify = getpass("Confirm Network Admin PWD : ")` | |   | `if pwd != pwd_verify:` | |   | `print("!!! Network Passwords do not match. Please try again.")` | |   | `print("-"*80)` | |   | `pwd = None` | |   | `# Trigger get_secret function to run` | |   | `get_secret(p2)` | |   | `return uid, pwd, secret` | |   | `# Trigger get_Credential function to run` | |   | `get_credentials()` | |   | 现在你已经完成了上面的任务 A,继续任务 b。 | | **6** | B.复制粘贴`read_info8.py`(`tool3_read_csv`);这是一个脚本,它读取`devices_info.csv`文件来填充我们的脚本,并将读取的项目转换成变量,以便在整个脚本中使用。把它变成一个函数,以提高可移植性和可读性。确保您传递了从任务 A 接收的三个参数,以便脚本运行时没有参数错误。记住,这个应用需要来自前面工作流的三个参数:`uid`、`pwd`和`secret`。 | |   | `"""` | |   | `Step 2\. From read_info8.py (tool3_read_csv) application:` | |   | `Read the content of the devices_info.csv file and convert the values into two lists. device_list to be used as an information feeder to the script device_list_netmiko to be used as netmiko friendly dictionary items for SSH connection` | |   | `"""` | |   | `def read_info(uid, pwd, secret):` | |   | `df = pd.read_csv(r'./devices_info.csv')` `# ensure the correct file location` | |   | `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'])` | |   | `# Append the items and convert to a list, device_list` | |   | `global device_list` `# For md5_validate3.py` | |   | `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)` | |   | `# Using device_list, create a netmiko friendly list device_list_netmiko` | |   | `global device_list_netmiko` | |   | `device_list_netmiko = []` | |   | `i = 0` | |   | `for x in device_list:` | |   | `if len(x) != 0:` `# As long as number of items in device_list is not 0 (empty)` | |   | `i += 1` | |   | `name = f'device{str(i)}'` `# Each for loop, the name is updated to device1, device2, device3, ...` | |   | `devicetype, host = x[2], x[3]` | |   | `device = {` | |   | `'device_type': devicetype,` | |   | `'host': host,` | |   | `'username': uid,` | |   | `'password': pwd,` | |   | `'secret': secret,` | |   | `}` | |   | `device_list_netmiko.append(device)` | |   | `# Trigger read_info function to run` | |   | `read_info(uid, pwd, secret)` | | **7** | C.这个模块几乎是直出`ping_tool6.py`和`ping_tool6_tools.py` ( `tool1_ping`)。复制脚本并进行修改;然后用它来测试设备列表中所有设备的连通性。这个脚本使用 Linux 的 OS `ping`命令和套接字网络来检查打开的套接字 22 状态。如果 IP 地址不可达,脚本将退出应用,您必须排除特定设备在网络上不可达的原因。这个脚本应该生成三个包含可达性状态的独立文件。如果端口 22 是开放的,那么设备的 IP 地址被写入`f1`;如果端口 22 关闭,脚本将测试端口 23 的状态,并将 IP 地址写入`f2`。如果设备不可达,则 IP 地址将被写入`f3`。 | |   | `"""` | |   | `Step 3\. From ping_tool6.py and ping_tool6_tools.py (tool1_ping) application:` | |   | `Perform connectivity tests, first using ICMP ping, Second, check port 22\. Also, separate the ips with open port 22\. If port 22 is closed, then test port 23 create three files containing the result of the reachability tests` | |   | `"""` | |   | `def test_connectivity(device_list_netmiko):` | |   | `f1 = open('reachable_ips_ssh.txt',  'w+')` | |   | `f2 = open('reachable_ips_telnet.txt', 'w+')` | |   | `f3 = open('unreachable_ips.txt', 'w+')` | |   | `for device in device_list_netmiko:` | |   | `ip = device['host'].strip()` | |   | `print(ip)` | |   | `resp = os.system('ping -c 4 ' + ip)` | |   | `if resp == 0:` | |   | `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")` | |   | `print("-"*80)` | |   | `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")` | |   | `print("-"*80)` | |   | `f2.write(f"{ip}\n")` | |   | `except:` | |   | `print(f"{ip} {port} closed")` | |   | `print("-"*80)` | |   | `f3.write(f"{ip} {port} closed\n")` | |   | `else:` | |   | `print(f"{ip} unreachable")` | |   | `print("-"*80)` | |   | `f3.write(f"{ip} unreachable\n")` | |   | `f1.close()` | |   | `f2.close()` | |   | `f3.close()` | |   | `# Trigger test_connectivity function to run` | |   | `test_connectivity(device_list_netmiko)` | | **8** | D.这部分来自`md5_validate3.py` ( `tool4_md5_linux`),如前所述,这将验证服务器上新 IOS 文件的 MD5 值。由于文件和脚本从一个 Python 自动化服务器运行,我们可以使用 Python 的`hashilib`来测试目录中 IOS 的 MD5 值。在整个工作流程中严格测试 MD5 值将保证 IOS 成功升级到更新的 IOS XE 版本。此外,您必须确保您的新 IOS 位于正确的目录中。 | |   | `"""` | |   | `Step 4\. From md5_validate3.py (tool4_md5_linux) application` `:` | |   | `Validate the MD5 value of the new IOS file on the Python Server against the value you have provided on the csv file. Must be validated before moving on.` | |   | `"""` | |   | `def validate_md5(device_list):` | |   | `for x in device_list:` | |   | `print(x[3], x[0], x[1], x[2])` | |   | `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')` `# New IOS directory here` | |   | `content = file.read()` | |   | `newiosmd5hash.update(content)` | |   | `newiosmd5server = newiosmd5hash.hexdigest()` | |   | `print(newiosmd5server.strip())` | |   | `global newiossize` | |   | `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!")` | |   | `print("-"*80)` | |   | `else:` | |   | `print("Mismatched MD5 values. Exit")` | |   | `print("-"*80)` | |   | `exit()` | |   | `return newiossize` | |   | `# Trigger validate_md5 function to run` | |   | `validate_md5(device_list)` | | **9** | E.在这个任务中,剧本几乎就是`check_flash2.py` ( `tool5_fsize_cisco`)的复写本。当您创建函数时,请确保您解析了前面工作流中的正确变量。在这种情况下,我们在 SSH 连接的`device_list_netmiko`列表和变量`newiossize`中进行解析,以确认路由器在闪存中有足够的空闲空间来容纳新的 IOS 文件大小。如果可用空间小于新 IOS 文件大小的 1.5 倍,脚本将退出。在较新的 Cisco 设备上,闪存空闲大小不是太大的挑战,因为它们可以容纳多个较大的 IOS 和 IOS XE 文件,但在较旧的设备上,您必须从闪存中删除旧的 IOS 以腾出空间。你可以在这里写一些有趣的东西,如果你愿意,可以自动定位最大或最老的文件。无论如何,在您的变更计划过程中,总是要检查空闲空间的大小。 | |   | `"""` | |   | `Step 5\. From check_flash2.py (tool5_fsize_cisco) application` `:` | |   | `Checks available flash size on the router` | |   | `"""` | |   | `def check_flash(device_list_netmiko, newiossize):` | |   | `for device in device_list_netmiko:` | |   | `ip = str(device['host'])` | |   | `net_connect = ConnectHandler(**device)` | |   | `net_connect.send_command("terminal length 0")` | |   | `showdir = net_connect.send_command("dir")` | |   | `#showflash = net_connect.send_command("show flash:")` `# For switches` | |   | `time.sleep(2)` | |   | `p1 = re.compile("\d+(?=\sbytes\sfree\))")` | |   | `m1 = p1.findall(showdir)` | |   | `flashfree = ((int(m1[0])/1000000))` | |   | `print(f"{ip} Free flash size : ", flashfree, "MB")` | |   | `print("-"*80)` | |   | `if flashfree < (newiossize * 1.5):` | |   | `print(f"Not enough space on {ip}'s flash! Exiting")` | |   | `print("!"*80)` | |   | `exit()` | |   | `else:` | |   | `print(f"{ip} has enough space for new IOS.")` | |   | `print("-"*80)` | |   | `# Trigger check_flash function to run` | |   | `check_flash(device_list_netmiko, newiossize)` | | **10** | F.这个脚本来自`upload_ios1.py` ( `tool7_upload_ios`),它构成了主脚本的一大部分。顾名思义,在前面的任务之后,应用准备将新的 IOS 上传到第一个路由器的闪存,但是在上传文件之前,需要进行一些整理工作。首先,登录用户拥有 15 级权限,可以通过 SCP 执行 IOS 上传任务。第二,路由器具有正确的配置,允许对该用户进行`aaa`认证和授权。如果不满足第一个和第二个要求,您就忽略了您的变更准备,因此您必须在尝试运行此应用之前先返回并解决访问问题。但是如果一切都正确配置了对该设备的正确访问,那么应用检查`ip scp server enable`以打开 Cisco 设备上的 SCP 服务器服务。如果此功能被禁用,脚本将在新的 IOS 文件上传之前自动启用它,然后在上传完成后禁用它。请注意,下一个任务将在此任务结束时继续。 | |   | `"""` | |   | `Step 6\. From upload_ios1.py (tool7_upload_ios) application` `:` | |   | `First, check the aaa configuration requirements. If not met, exit for correct configuration. Second, check if 'ip scp enable' is enabled for SCP file transfer. Enable ip scp.` | |   | `Third, upload the IOS file to the router, then disable ip scp.` | |   | `"""` | |   | `def ios_upload(device_list_netmiko, device_list):` | |   | `i = 0` | |   | `for device in device_list_netmiko:` | |   | `device_x = device_list[i]` | |   | `newios = device_x[4] # Call out newios value` | |   | `newiosmd5 = device_x[5] # Call out newiosmd5 value` | |   | `i += 1` | |   | `s_newios = f'/home/pynetauto/my_tools/new_ios/{newios}'` | |   | `print("*"*80)` | |   | `#print(device)` | |   | `ip = str(device['host'])` | |   | `print(f'{ip} beginning IOS XE upgrade.')` | |   | `username = str(device['username'])` | |   | `net_connect = ConnectHandler(**device)` | |   | `net_connect.send_command("terminal length 0")` | |   | `showrun = net_connect.send_command("show running-config")` | |   | `check_priv15 = (f'username {username} privilege 15')` | |   | `aaa_authenication = "aaa authentication login default local enable"` | |   | `aaa_authorization = "aaa authorization exec default local"` | |   | `if check_priv15 in showrun` `:` | |   | `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")` | |   | `else:` | |   | `print("aaa_authorization - FAILED ")` | |   | `exit()` | |   | `else:` | |   | `print("aaa_authentication - FAILED ")` | |   | `exit()` | |   | `else:` | |   | `print(f"{username} has not enough privilege - FAILED")` | |   | `exit()` | |   | `net_connect.enable(cmd='enable 15')` | |   | `net_connect.config_mode()` | |   | `net_connect.send_command('ip scp server enable')` | |   | `net_connect.exit_config_mode()` | |   | `time.sleep(1)` | |   | `print("New IOS uploading in progress! Please wait...")` | |   | `scp_conn = SCPConn(net_connect)` | |   | `scp_conn.scp_transfer_file(s_newios, newios)` | |   | `scp_conn.close()` | |   | `time.sleep(1)` | |   | `net_connect.config_mode()` | |   | `net_connect.send_command('no ip scp server enable')` | |   | `net_connect.exit_config_mode()` | | **11** | G.在这个任务中,你可以借用`md5_verify2.py`(在`tool8_md5_cisco`中)的脚本对路由器 flash 上新上传的 IOS 进行 MD5 验证,并与服务器上计算的 MD5 值进行比较。我们再次利用正则表达式的能力,通过检查命令输出中的“已验证”字符来测试 Cisco IOS XE 的`verify`命令是否成功运行。然后我们使用`re`(正则表达式)编译器找到 32 个字符的 MD5 值,并将其作为一个变量来测试 MD5 值的有效性。如果出现任何错误,`try`和`except`会捕捉到大部分错误。 | |   | `"""` | |   | `Step 7\. From md5_verify2.py (in tool8_md5_cisco) application` `:` | |   | `Fourth, verify the MD5 value of the new IOS on the router flash, then` | |   | `compare it with the server-side MD5 value.` | |   | `"""` | |   | `try:` | |   | `locate_newios = net_connect.send_command(f"show flash: | in {newios}")` | |   | `if newios in locate_newios:` | |   | `result = net_connect.send_command("verify /md5 flash:{} {}".format(newios,newiosmd5))` | |   | `print(result)` | |   | `p1 = re.compile(r'Verified')` | |   | `p2 = re.compile(r'[a-fA-F0-9]{31}[a-fA-F0-9]')` | |   | `verified = p1.findall(result)` | |   | `newiosmd5flash = p2.findall(result)` | |   | `if verified:` | |   | `result = True` | |   | `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:` | |   | `result = False` | |   | `print("MD5 values DO NOT MATCH! Exiting.")` | |   | `print("-"*80)` | |   | `exit()` | |   | `else:` | |   | `print("No new IOS found on router's flash. Continue to next device...")` | |   | `print("-"*80)` | |   | `except (NetMikoTimeoutException):` | |   | `print (f'Timeout error to : {ip}')` | |   | `print("-"*80)` | |   | `continue` | |   | `except unknown_error:` | |   | `print ('Unknown error occurred : ' + str(unknown_error))` | |   | `print("-"*80)` | |   | `continue` | | **12** | H.在正常情况下,这可能不是必需的,但为了让应用更真实,我决定让这个应用更具交互性,在新的 IOS 上传完成后,它会给用户一个选项,通过回答`yes`或`no`来重新加载路由器。这是脚本的一大部分。只要您用`y`或`yes`做出响应并按下回车键,脚本就会重新配置路由器的引导系统配置,然后将运行配置保存到启动配置中。接下来是四个`show`命令的预加载配置捕获。它发出一个`reload`命令来启动设备的重新加载。一旦路由器进入重载模式,您将失去 SSH 连接,并且 Python 自动化服务器将返回`EoFError`。为了继续在服务器上运行我们的脚本,我们使用`try`和`except`方法来捕捉这个错误并等待最初的 60 秒;然后我们运行`check_port_22_post`函数,每 5 秒钟扫描一次端口 22 的状态。`check_port_22_post`应用是智能的,因此它会自动检测路由器是否启动,并执行重新加载后的配置检查;然后它会产生多个 HTML 比较文件,用于 IOS 升级验证。 | |   | `"""` | |   | `Step 8\. From reload_yesno2.py (in tool9_yes_no) application` `:` | |   | `Fifth, give the user a chance to review the result and let` | |   | `him/her decide if he wants to reload the device. This can be removed to automate this process fully, but this is included to demonstrate how interactive session works. If 'Y", reload now, if "n", reload later.       Sixth, change the boot system command to boot into new IOS on the flash:/ Save configuration.` | |   | `"""` | |   | `yes_list = ['yes', 'y']` | |   | `no_list = ['no', 'n']` | |   | `print("!"*80)` | |   | `print("Note: Devices will be reloaded one at a time.")` | |   | `resp = input(f"Do you want to reload {ip} now? (y/n)? ").lower()` | |   | `if resp in yes_list:` | |   | `print("Reloading devices")` | |   | `#net_connect = ConnectHandler(**device)` | |   | `net_connect.enable(cmd='enable 15')` | |   | `config_commands1 = ['no boot system', 'boot system flash:/' + 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 newios not in show_dir:` | |   | `print('Unable to locate new IOS on the flash:/. Exiting.')` | |   | `print("-"*80)` | |   | `exit()` | |   | `elif newios not in show_boot:` | |   | `print('Boot system was not correctly configured. Exiting.')` | |   | `print("-"*80)` | |   | `exit()` | |   | `# Seventh, if boot commands are correct, capture another running backup of the device for comparison.` | |   | `elif newios in show_boot and newios in show_dir:` | |   | `try:` | |   | `print(f'Found {newios} in show boot')` | |   | `print("-"*80)` | |   | `print(f"{ip} [Pre-reload] Capture configuration")` | |   | `net_connect.send_command("terminal length 0")` | |   | `time.sleep(1)` | |   | `with open(f'{ip}_showver_pre.txt', 'w+') as f1:` | |   | 4 `print("* [Pre-reload] Capturing 'show version'")` | |   | 4 `showver_pre = net_connect.send_command("show version")` | |   | 4 `f1.write(showver_pre)` | |   | `time.sleep(1)` | |   | `with open(f'{ip}_showrun_pre.txt', 'w+') as f2:` | |   | 4 `print("* [Pre-reload] Capturing 'show running-config'")` | |   | 4 `showrun_pre = net_connect.send_command("show running-config")` | |   | 4 `f2.write(showrun_pre)` | |   | `time.sleep(1)` | |   | `with open(f'{ip}_showint_pre.txt', 'w+') as f3:` | |   | 4 `print("* [Pre-reload] Capturing 'show ip interface brief'")` | |   | 4 `showint_pre = net_connect.send_command("show ip interface brief")` | |   | 4 `f3.write(showint_pre)` | |   | `time.sleep(1)` | |   | `with open(f'{ip}_showroute_pre.txt', 'w+') as f4:` | |   | 4 `print("* [Pre-reload] Capturing 'show ip route'")` | |   | 4 `showroute_pre = net_connect.send_command("show ip route")` | |   | 4 `f4.write(showroute_pre)` | |   | `time.sleep(1)` | |   | `print("-"*80)` | |   | `t1 = time.mktime(time.localtime())` `# To measure time elapsed for reload` | |   | `# Eighth, initiate the device reload` | |   | `print(f"{ip} is now reloading.")` | |   | `net_connect.send_command('reload', expect_string="[confirm]")` | |   | `net_connect.send_command('yes')` | |   | `net_connect.send_command('\n')` | |   | `print("-"*80)` | |   | `# If you remove the "y" or "n" prompt when reloading is initiated, the server will generate` | |   | `# OSError, use this code to catch the OSError to run the code continuously.` | |   | `# If using the interactive as in this example, then it will produce EoFError, so leave this` | |   | `# code as is` | |   | `# except OSError:` | |   | `# print("***Device is now reloading. This may take 2-5 minutes.")` | |   | `# time.sleep(45)` | |   | `# print("-"*80)` | |   | `# port = 22` | |   | `# check_port_22_post(ip, port, device, t1)` | |   | `# print("-"*80)` | |   | `except EOFError:` | |   | `print("***Device is now reloading. This may take 2-5 minutes.")` | |   | `time.sleep(60)` | |   | `print("-"*80)` | |   | `port = 22` | |   | `# Trigger check_port_22_post function for check port 22 and post check` | |   | `check_port_22_post(ip, port, device, t1)` | |   | `print("-"*80)` | |   | `# If chosen 'n', break out of the operation` | |   | `elif resp in no_list:` | |   | `print("You have chosen to reload the devices later. Exiting the application.")` | |   | `break` | |   | `print("*"*80)` | |   | `# Trigger ios_upload main function` | |   | `ios_upload(device_list_netmiko, device_list)` | | **13** | 这个脚本需要放在主脚本之前,主脚本需要在同一个文件中被调用。将这个脚本放在 task A 之前,这样这个函数就在主脚本运行之前被预定义了。这个脚本将作为主脚本中的最终脚本运行,所以 Python 必须知道这个脚本。之前,我们简要讨论了这个脚本是如何工作的,但这是与`post_check2.py` ( `tool10_post_check`)相同的脚本。应用的这一部分将使用 5 秒的`delay`(等待)计时器和 90(次)的`retry`值检查端口 22 是打开还是关闭。典型操作下的 IOS-XE 路由器和虚拟路由器(如 CSR 1000v)将在大约三到五分钟内完成重新加载。但是,如果启用了多个路由协议,并且大量 IP 流量通过生产网络,则可能需要更长时间。 | |   | 一旦路由器接口恢复正常,并且检测到端口 22 为连接开放,该应用将通过 SSH 返回到路由器,执行重新加载后任务,并在重新加载后捕获四个`show`配置。使用`difflib`库比较两组文件,pre 和 post。该库为 IOS 升级成功验证和确认创建了四组 HTML 文件。这基本上意味着重用一个`make_backup1.py` ( `tool6_make_backup`)应用。在此任务中,两个应用合并为一个。如果有另一台设备,在这种情况下,是的,脚本将返回到任务 G,并开始在第二台路由器上上传 IOS,并遵循与之前相同的工作流程。如果没有其他设备可用于 IOS 升级,应用将完成并输出成功消息,并且从头到尾运行整个脚本也需要时间。 | |   | `"""` | |   | `Step 9\. From post_check2.py (tool10_post_check) and make_backup1.py (tool6_make_backup)` `.` | |   | `Check SSH port 22 and then log back in. Then complete the post-upgrade verification check. This script compares before and after running configurations to validate the IOS XE upgrade's success for your devices.` | |   | `"""` | |   | `def check_port_22_post(ip, port, device, t1):` | |   | `retry = 90 # Try 90 times` | |   | `delay = 5 # 5 seconds wait` | |   | `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` `# The statements after the return statements are not executed.` | |   | `except:` | |   | `return False` | |   | `finally:` | |   | `s.close()` | |   | `# Retry 90 times until the port is in open state.` | |   | `for i in range(retry):` | |   | `if isOpen(ip, port):` | |   | `print(ip + " is back online.")` | |   | `t2 = time.mktime(time.localtime()) - t1` | |   | `print(f"{ip}'s reload time : {0} seconds".format(t2))` | |   | `print(f"Logging back into {ip} to perform post-reload tasks.")` | |   | `print("Please wait...")` | |   | `# When port 22 is open, start postreload_check here.` | |   | `print(ip)` | |   | `net_connect = ConnectHandler(**device)` | |   | `print(f'*** {ip} is back online.')` | |   | `print("-"*80)` | |   | `# [Post-reload] Capture configuration` | |   | `print(f"{ip} [Post-reload] Capture configuration")` | |   | `file14 = open(ip + '_showver_post.txt', 'w+')` | |   | `net_connect.send_command("terminal length 0")` | |   | `showver_postreload = net_connect.send_command("show version")` | |   | `print("* [Post-reload] Capturing 'show version'")` | |   | `file14.write(showver_postreload)` | |   | `file14.close()` | |   | `# Successful upgrade will print IP and correct system boot image name.` | |   | `p99= re.compile(r'System image file is ".+[?=\"]')` | |   | `m99 = p99.findall(showver_postreload)` | |   | `print(f"! {ip}'s {m99}.")` | |   | `time.sleep(2)` | |   | `file15 = open(ip + '_showrun_post.txt', 'w+')` | |   | `showrun_postreload = net_connect.send_command("show running-config")` | |   | `print("* [Post-reload] Capturing 'show running-config'")` | |   | `file15.write(showrun_postreload)` | |   | `file15.close()` | |   | `time.sleep(2)` | |   | `file16 = open(ip + '_showint_post.txt', 'w+')` | |   | `showint_postreload = net_connect.send_command("show ip interface brief")` | |   | `print("* [Post-reload] Capturing 'show ip interface brief'")` | |   | `file16.write(showint_postreload)` | |   | `file16.close()` | |   | `time.sleep(2)` | |   | `file17= open(ip + '_showroute_post.txt', 'w+')` | |   | `showroute_postreload = net_connect.send_command("show ip route")` | |   | `print("* [Post-reload] Capturing 'show ip route'")` | |   | `file17.write(showroute_postreload)` | |   | `file17.close()` | |   | `time.sleep(2)` | |   | `# Compare pre and post configurations to create four html files` | |   | `showver_pre = "showver_pre"` | |   | `showver_post = "showver_post"` | |   | `showver_pre_lines = open(ip+ '_showver_pre.txt').readlines()` `# Convert to strings first for` | |   | `comparison` | |   | `time.sleep(1)` | |   | `showver_post_lines = open(ip+ '_showver_post.txt').readlines()` `#Convert to strings first for` | |   | `comparison` | |   | `time.sleep(1)` | |   | `# Note, 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(ip + "_show_ver_comparison.html", "w+")` | |   | `difference_report.write(difference) # Write the differences to the difference_report` | |   | `difference_report.close()` | |   | `time.sleep(1)` | |   | `showrun_pre = "showrun_pre"` | |   | `showrun_post = "showrun_post"` | |   | `showrun_pre_lines = open(ip+ '_showrun_pre.txt').readlines()` `# Convert to strings first for` | |   | `comparison` | |   | `time.sleep(1)` | |   | `showrun_post_lines = open(ip+ '_showrun_post.txt').readlines()` `# Convert to strings first for` | |   | `comparison` | |   | `time.sleep(1)` | |   | `difference = difflib.HtmlDiff(wrapcolumn=70).make_file(showrun_pre_lines,` | |   | `showrun_post_lines, showrun_pre, showrun_post)` | |   | `difference_report = open(ip + "_show_run_comparison.html", "w+")` | |   | `difference_report.write(difference)` `# Write the differences to the difference_report` | |   | `difference_report.close()` | |   | `time.sleep(1)` | |   | `showint_pre = "showint_pre"` | |   | `showint_post = "showint_post"` | |   | `showint_pre_lines = open(ip+ '_showint_pre.txt').readlines()` | |   | `time.sleep(1)` | |   | `showint_post_lines = open(ip+ '_showint_post.txt').readlines()` | |   | `time.sleep(1)` | |   | `difference = difflib.HtmlDiff(wrapcolumn=70).make_file(showint_pre_lines,` | |   | `showint_post_lines, showint_pre, showint_post)` | |   | `difference_report = open(ip + "_show_int_comparison.html", "w+")` | |   | `difference_report.write(difference)` | |   | `difference_report.close()` | |   | `time.sleep(1)` | |   | `showroute_pre = "showroute_pre"` | |   | `showroute_post = "showroute_post"` | |   | `showroute_pre_lines = open(ip+ '_showroute_pre.txt').readlines()` | |   | `time.sleep(1)` | |   | `showroute_post_lines = open(ip+ '_showroute_post.txt').readlines()` | |   | `time.sleep(1)` | |   | `difference = difflib.HtmlDiff(wrapcolumn=70).make_file(showroute_pre_lines,` | |   | `showroute_post_lines, showroute_pre, showroute_post)` | |   | `difference_report1 = open(ip + "_show_route_comparison.html", "w+")` | |   | `difference_report1.write(difference)` | |   | `difference_report1.close()` | |   | `time.sleep(1)` | |   | `print("-"*80)` | |   | `break` | |   | `else:` | |   | `print(f'{ip} is still reloading. Please wait...')` | |   | `time.sleep(delay)` | | four | J.将以下脚本附加到任务 H 脚本的末尾。当最终应用成功运行时,打印功能中的语句将打印在用户屏幕上,包括整个 IOS 升级过程所用的时间。这些是 IOS 升级应用中的最后几行代码。 | |   | **print("完成新 IOS 验证。")** | |   | `print("All tasks completed successfully!")` | |   | `tt = time.mktime(time.localtime()) - t # Timer finish to measure script running time` | |   | `print("Total time taken : {0} seconds".format(tt)) # Informational` | | five | 还记得乐高积木吗?就像用乐高积木一样,我们把小片段的脚本转换成一个大的脚本。现在我们有了一个用 Python 编写的工作应用。你可以在我的 GitHub 下载页面上找到完整的脚本供参考。 | |   | URL: [`https://github.com/pynetauto/apress_pynetauto`](https://github.com/pynetauto/apress_pynetauto) | |   | 一旦您完成了 Cisco IOS XE 升级应用的构建,按照这里所示运行它,您应该会得到类似所示的结果。在运行完整的应用之前,确保在`csr1000v-1`和`csr1000v-2`第十八章上传的新 IOS 文件已经从闪存中删除。如果该文件存在于闪存中,脚本仍会覆盖它,但建议您从路由器的闪存中删除测试文件,以提高 IOS 升级测试的成功率。我们还可以编写一些代码来检查是否存在相同的 IOS 文件,并跳过 IOS 上传过程,但在生产网络中,您应该只信任自己准备和上传的文件。因此,让这个脚本覆盖任何预先存在的文件。 | |   | K.现在,让我们运行最后的脚本。记住,IOS 上传到 flash 后,会提示您选择`Yes`或`No`来重新加载路由器。选择`y`表示是。 | |   | `pynetauto@ubuntu20s1:~/my tools/ios_upgrade_tool$` `python3 upgrade_crs1000v.py` | |   | `Enter Network Admin ID :` `pynetauto` | |   | `Enter Network Admin PWD : ********` | |   | `Confirm Network Admin PWD : ********` | |   | `Is the secret same as the password? (y/n) : y # Type in 'y' for same secret password` | |   | `--------------------------------------------------------------------------------` | |   | `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.57 ms` | |   | `64 bytes from 192.168.183.111: icmp_seq=3 ttl=255 time=1.61 ms` | |   | `64 bytes from 192.168.183.111: icmp_seq=4 ttl=255 time=1.62 ms` | |   | `--- 192.168.183.111 ping statistics ---` | |   | `4 packets transmitted, 3 received, 25% packet loss, time 3019ms` | |   | `rtt min/avg/max/mdev = 1.573/1.599/1.617/0.019 ms` | |   | `192.168.183.111 22 open` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.222` | |   | `PING 192.168.183.222 (192.168.183.222) 56(84) bytes of data.` | |   | `64 bytes from 192.168.183.222: icmp_seq=1 ttl=255 time=1.44 ms` | |   | `64 bytes from 192.168.183.222: icmp_seq=2 ttl=255 time=1.61 ms` | |   | `64 bytes from 192.168.183.222: icmp_seq=3 ttl=255 time=1.58 ms` | |   | `64 bytes from 192.168.183.222: icmp_seq=4 ttl=255 time=1.56 ms` | |   | `--- 192.168.183.222 ping statistics ---` | |   | `4 packets transmitted, 4 received, 0% packet loss, time 3006ms` | |   | `rtt min/avg/max/mdev = 1.438/1.545/1.607/0.064 ms` | |   | `192.168.183.222 22 open` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.111 csr1000v-1 RT cisco_xe` | |   | `77878ae6db8e34de90e2e3e83741bf39` | |   | `77878ae6db8e34de90e2e3e83741bf39` | |   | `436.57 MB` | |   | `MD5 values matched!` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.222 csr1000v-2 RT cisco_xe` | |   | `77878ae6db8e34de90e2e3e83741bf39` | |   | `77878ae6db8e34de90e2e3e83741bf39` | |   | `436.57 MB` | |   | `MD5 values matched!` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.111 Free flash size :  6623.248384 MB` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.111 has enough space for a new IOS.` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.222 Free flash size :  6623.55968 MB` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.222 has enough space for a new IOS.` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.111 original config backup completed.` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.222 original config backup completed.` | |   | `--------------------------------------------------------------------------------` | |   | `********************************************************************************` | |   | `192.168.183.111 beginning IOS XE upgrade` `.` | |   | `pynetauto has level 15 privilege - OK` | |   | `check_aaa_authentication - OK` | |   | `check_aaa_authorization - OK` | |   | `New IOS uploading in progress! Please wait...` | |   | `............................................................................................................................................................................` | |   | `[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` | |   | `--------------------------------------------------------------------------------` | |   | `!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!` | |   | `Note: Devices will be reloaded one at a time.` | |   | `Do you want to reload 192.168.183.111 now? (y/n)? y # Type in 'y' to reload csr1000v-1` | |   | `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` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.111 [Pre-reload] Capture configuration` | |   | `* [Pre-reload] Capturing 'show version'` | |   | `* [Pre-reload] Capturing 'show running-config'` | |   | `* [Pre-reload] Capturing 'show ip interface brief'` | |   | `* [Pre-reload] Capturing 'show ip route'` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.111 is now reloading.` | |   | `***Device is now reloading. This may take 2-5 minutes.` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.111 is still reloading. Please wait...` | |   | `192.168.183.111 is still reloading. Please wait...` | |   | `192.168.183.111 is still reloading. Please wait...` | |   | `192.168.183.111 is still reloading. Please wait...` | |   | `192.168.183.111 is still reloading. Please wait...` | |   | `192.168.183.111 is still reloading. Please wait...` | |   | `192.168.183.111 is still reloading. Please wait...` | |   | `192.168.183.111 is still reloading. Please wait...` | |   | `192.168.183.111 is still reloading. Please wait...` | |   | `192.168.183.111 is still reloading. Please wait...` | |   | `192.168.183.111 is back online.` | |   | `192.168.183.111's reload time : 0 seconds` | |   | `Logging back into 192.168.183.111 to perform post-reload tasks.` | |   | `Please wait...` | |   | `192.168.183.111` | |   | `*** 192.168.183.111 is back online.` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.111 [Post-reload] Capture configuration` | |   | `* [Post-reload] Capturing 'show version'` | |   | `! 192.168.183.111's ['System image file is "bootflash:/csr1000v-universalk9.16.09.06.SPA.bin"'].` | |   | `* [Post-reload] Capturing 'show running-config'` | |   | `* [Post-reload] Capturing 'show ip interface brief'` | |   | `* [Post-reload] Capturing 'show ip route'` | |   | `--------------------------------------------------------------------------------` | |   | `--------------------------------------------------------------------------------` | |   | `********************************************************************************` | |   | `********************************************************************************` | |   | `192.168.183.222 beginning IOS XE upgrade.` | |   | `pynetauto has level 15 privilege - OK` | |   | `check_aaa_authentication - OK` | |   | `check_aaa_authorization - OK` | |   | `New IOS uploading in progress! Please wait...` | |   | `................................................................................` | |   | `[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` | |   | `--------------------------------------------------------------------------------` | |   | `!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!` | |   | `Note: Devices will be reloaded one at a time.` | |   | `Do you want to reload 192.168.183.222 now? (y/n)? y # Type in 'y' to reload csr1000v-2` | |   | `Reloading devices` | |   | `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#` | |   | `Found csr1000v-universalk9.16.09.06.SPA.bin in show boot` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.222 [Pre-reload] Capture configuration` | |   | `* [Pre-reload] Capturing 'show version'` | |   | `* [Pre-reload] Capturing 'show running-config'` | |   | `* [Pre-reload] Capturing 'show ip interface brief'` | |   | `* [Pre-reload] Capturing 'show ip route'` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.222 is now reloading.` | |   | `***Device is now reloading. This may take 2-5 minutes.` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.222 is still reloading. Please wait...` | |   | `192.168.183.222 is still reloading. Please wait...` | |   | `192.168.183.222 is still reloading. Please wait...` | |   | `192.168.183.222 is still reloading. Please wait...` | |   | `192.168.183.222 is still reloading. Please wait...` | |   | `192.168.183.222 is still reloading. Please wait...` | |   | `192.168.183.222 is still reloading. Please wait...` | |   | `192.168.183.222 is still reloading. Please wait...` | |   | `192.168.183.222 is still reloading. Please wait...` | |   | `192.168.183.222 is still reloading. Please wait...` | |   | `192.168.183.222 is back online.` | |   | `192.168.183.222's reload time : 0 seconds` | |   | `Logging back into 192.168.183.222 to perform post-reload tasks.` | |   | `Please wait...` | |   | `192.168.183.222` | |   | `*** 192.168.183.222 is back online.` | |   | `--------------------------------------------------------------------------------` | |   | `192.168.183.222 [Post-reload] Capture configuration` | |   | `* [Post-reload] Capturing 'show version'` | |   | `! 192.168.183.222's ['System image file is "bootflash:/csr1000v-universalk9.16.09.06.SPA.bin"'].` | |   | `* [Post-reload] Capturing 'show running-config'` | |   | `* [Post-reload] Capturing 'show ip interface brief'` | |   | `* [Post-reload] Capturing 'show ip route'` | |   | `--------------------------------------------------------------------------------` | |   | `--------------------------------------------------------------------------------` | |   | `********************************************************************************` | |   | `Completed new IOS verification.` | |   | `All tasks completed successfully!` | |   | `Total time taken : 976.0 seconds # = 16.27 minutes in total` | 在`csr1000v-1`和`csr1000v-2`成功完成 IOS XE 升级后,你会发现`/home/pynetauto/my_tools/`目录下的所有文件(见图 19-1 )。复制并打开文件,检查您的 IOS 升级是否成功。现在,您已经使用手写的 Python 网络自动化代码升级了两台 IOS-XE 路由器。我希望你喜欢这次经历。 要观看思科 IOS-XE 升级的演示视频,请访问 [`https://www.youtube.com/watch?v=tnNEeXAGd0M`](https://www.youtube.com/watch%253Fv%253DtnNEeXAGd0M) 。 ![img/492721_1_En_19_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-python-pt2-zh/raw/master/docs/intro-py-net-auto/img/492721_1_En_19_Fig1_HTML.jpg) 图 19-1。 *WinSCP,复制文件以验证成功的 Cisco 路由器 IOS-XE 升级* ## 摘要 在真实的生产环境中,脚本被分成不同的功能脚本,称为*模块*。保持主脚本轻量级的另一种方法是将每个应用作为不同的模块保存在另一个文件中。我选择用一个 Python 脚本来创建整个应用,以帮助您理解它是如何工作的,但是您可能已经注意到使用冗长的脚本代码是多么低效。虽然我可以继续在几个单独的模块或文件中编写这些代码,并将这些函数作为一个模块重新导入主脚本,但是我将把这个挑战留给您。 我希望您的第一次 Python 网络自动化之旅是一个惊喜,并且您正在期待您旅程的下一部分。总之,我们在本书中涉及了很多内容,但可能我们也留下了许多有待改进的漏洞。你不可能在一夜之间变得擅长某事;写 Python 代码不像中彩票或在 YouTube 上一夜成名。对 Python 编程达到一定程度的信心需要几个月甚至几年的时间。记住约瑟夫·马歇尔的名言,“生活是一个圆圈。一段旅程的结束意味着另一段旅程的开始。” ## 最后的话 对于我们许多人来说,学习一门新的编程语言来让自己的职业生涯更上一层楼可能是一项艰巨的任务。这就像踏上了一段通往未知的新旅程。许多技术传播者鼓励人们学习一门编程语言,并从今天开始编写代码。一些福音传道者鼓吹 Python 适合所有人。在这里,我委婉地表示不同意。编程或编写代码并不适合所有人。然而,如果你花几个小时在电脑前工作,学习像 Python 这样的高级编程语言可以在工作中创造许多奇迹。如果你的工作涉及支持 IT 服务或基础设施,我找不到一个不学习 Python 编码的借口。虽然 Python 是最容易学的编程语言之一,但没有多少人谈论达到一定水平后会发生什么。Python 和所有其他编程语言都一样,Python 或另一种编程语言的聪明程度取决于编写代码的人。学习编程语言不能成为达到目的的手段。相反,这是另一个开始,或者是一个没有终点的新旅程。所以,你必须一直呆在学校里,寻找机会去应用你在旅途中学到的概念和技能。 在本书中,我试图为新的和现有的网络工程师编写一本实用的 Python 网络自动化书籍,涵盖从头构建基础设施和编写可以帮助他们更好地管理网络基础设施的工作应用所需的各种基本 IT 管理技能。我希望您已经广泛接触了各种技术,并且已经为更好的企业网络管理打下了坚实的基础。最后,感谢您购买我的书,开始您的 Python 网络自动化之旅。我想祝贺每一个在成功运行最终实验后到达此页面的人。即使你还在努力完成最后的实验,你仍然有足够的时间来复习这本书的内容,因为你现在已经拥有它了。
posted @ 2024-08-10 15:29  绝不原创的飞龙  阅读(2)  评论(0编辑  收藏  举报