WSL2-提示和技巧-全-
WSL2 提示和技巧(全)
原文:
zh.annas-archive.org/md5/5EBC4B193F90421D3484B13463D11C33
译者:飞龙
前言
Windows 子系统 Linux(WSL)是微软的一项令人兴奋的技术,它将 Linux 与 Windows 并列,并允许您在 Windows 上运行未经修改的 Linux 二进制文件。与在隔离的虚拟机中运行 Linux 的体验不同,WSL 带来了丰富的互操作能力,使您可以将每个操作系统的工具结合在一起,让您可以使用最适合工作的工具。
通过改进性能并提供完整的系统调用兼容性,WSL 2 使 WSL 得到了进一步的发展,为您提供了更多的功能。此外,其他技术,如 Docker Desktop 和 Visual Studio Code,已经添加了对 WSL 的支持,为您提供了更多利用它的方式。
通过 Docker Desktop 的 WSL 集成,您可以在 WSL 中运行 Docker 守护程序,从而提供一系列好处,包括在从 WSL 挂载卷时提高性能。
Visual Studio Code 中的 WSL 集成使您能够在 WSL 中安装项目工具和依赖项,以及源代码,并使 Windows 用户界面连接到 WSL 以加载代码并在 WSL 中运行和调试应用程序。
总的来说,WSL 是一项令人兴奋的技术,它极大地改善了我的日常工作流程,我希望在您阅读本书时能与您分享这种兴奋!
本书适合谁?
本书适用于希望在 Windows 上使用 Linux 工具的开发人员,包括根据项目要求希望逐渐适应 Linux 环境的本地 Windows 程序员,或最近切换到 Windows 的 Linux 开发人员。本书还适用于使用以 Ruby 或 Python 为首选的 Linux 工具进行开源项目的 Web 开发人员,或者希望在测试应用程序时在容器和开发机之间切换的开发人员。
本书涵盖了什么内容?
[第一章](B16412_01_Final_JC_ePub.xhtml#_idTextAnchor017),介绍 Windows 子系统 Linux,概述了 WSL 是什么,并探讨了 WSL 1 和 WSL 2 之间的区别。
[第二章](B16412_02_Final_JC_ePub.xhtml#_idTextAnchor023),安装和配置 Windows 子系统 Linux,带您了解安装 WSL 2 的过程,如何使用 WSL 安装 Linux 发行版,以及如何控制和配置 WSL。
[第三章](B16412_03_Final_JC_ePub.xhtml#_idTextAnchor037),开始使用 Windows 终端,介绍了新的 Windows 终端。这个来自微软的新的开源终端正在快速发展,并为在 WSL 2 中使用 shell 提供了很好的体验。您将了解如何安装 Windows 终端,如何使用它,并自定义其外观。
[第四章](B16412_04_Final_JC_ePub.xhtml#_idTextAnchor047),Windows 与 Linux 的互操作性,开始深入研究 WSL 提供的互操作性功能,看看如何从 Windows 访问 Linux 发行版中的文件和应用程序。
[第五章](B16412_05_Final_JC_ePub.xhtml#_idTextAnchor054),Linux 与 Windows 的互操作性,继续探索 WSL 的互操作性功能,展示如何从 Linux 访问 Windows 文件和应用程序,以及一些互操作性技巧和技巧。
[第六章](B16412_06_Final_JC_ePub.xhtml#_idTextAnchor069),获取更多 Windows 终端,探索了 Windows 终端的更多深入方面,例如自定义选项卡标题和将选项卡分割成多个窗格。您将看到各种选项,包括如何从命令行控制 Windows 终端(以及如何重用命令行选项与正在运行的 Windows 终端一起工作)。您还将了解如何添加自定义配置文件以提高日常工作流程。
第七章,在 WSL 中使用容器,介绍了使用 Docker Desktop 在 WSL 2 中运行 Docker 守护程序的方法。您将了解如何构建和运行用于示例 Web 应用程序的容器。本章还展示了如何启用并使用 Docker Desktop 中的 Kubernetes 集成,在 WSL 中运行示例 Web 应用程序。
第八章,使用 WSL 发行版,指导您完成导出和导入 WSL 发行版的过程。这种技术可用于将发行版复制到另一台计算机或在本地计算机上创建副本。您还将了解如何使用容器映像快速创建新的 WSL 发行版。
第九章,Visual Studio Code 和 WSL,在探索 Remote-WSL 扩展之前,对 Visual Studio Code 进行了简要介绍,该扩展可用于在 WSL 发行版文件系统中使用代码。通过这种方法,您可以在 WSL 中保留 Visual Studio Code 的丰富 GUI 体验,同时运行代码文件、工具和应用程序。
第十章,Visual Studio Code 和容器,通过查看 Remote-Containers 扩展继续探索 Visual Studio Code,该扩展允许您将所有项目依赖项打包到容器中。这种方法可以使项目之间的依赖项隔离,以避免冲突,并且还可以让新团队成员快速入门。
第十一章,使用命令行工具提高生产力,介绍了一些在命令行中使用 Git 的技巧,然后介绍了处理 JSON 数据的一些方法。之后,它探索了 Azure 和 Kubernetes 命令行实用程序以及它们各自用于查询信息的方法,包括进一步探索处理 JSON 数据。
为了充分利用本书的内容
要按照本书中的示例进行操作,您需要使用与 WSL 版本 2 兼容的 Windows 10 版本(请参阅下表)。您还需要 Docker Desktop 和 Visual Studio Code。
需要具备先前的编程或开发经验以及对在 PowerShell、Bash 或 Windows 命令提示符中运行任务的基本理解:
如果您使用的是电子版的本书,我们建议您自己输入代码或通过 GitHub 存储库访问代码(链接在下一节中提供)。这样做可以帮助您避免与复制和粘贴代码相关的潜在错误。
微软还宣布了 WSL 的其他功能(例如对 GPU 和 GUI 应用程序的支持),但在撰写本书时,这些功能尚不稳定,仅以早期预览形式提供。本书选择关注 WSL 的稳定发布功能,因此目前专注于 WSL 的当前、以命令行为中心的视图。
下载示例代码文件
您可以从 GitHub 上的以下链接下载本书的示例代码文件:github.com/PacktPublishing/Windows-Subsystem-for-Linux-2-WSL-2-Tips-Tricks-and-Techniques
。如果代码有更新,将在现有的 GitHub 存储库中进行更新。
我们还提供了来自我们丰富的图书和视频目录的其他代码包,可在github.com/PacktPublishing/
上获取。请查看!
下载彩色图像
我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在此处下载:static.packt-cdn.com/downloads/9781800562448_ColorImages.pdf
。
使用的约定
本书中使用了一些文本约定。
文本中的代码
:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这是一个例子:“要更改 UI 中配置文件的顺序,我们可以更改settings
文件中profiles
下的list
中的条目顺序。”
代码块设置如下:
"profiles": {
"defaults": {
"fontFace": "Cascadia Mono PL"
},
当我们希望引起您对代码块的特定部分的注意时,相关的行或项目将以粗体显示:
"profiles": {
"defaults": {
"fontFace": "Cascadia Mono PL"
},
任何命令行输入或输出都按以下方式编写:
git clone https://github.com/magicmonty/bash-git-prompt.git ~/.bash-git-prompt --depth=1
粗体:表示新术语、重要单词或屏幕上显示的单词。例如,菜单或对话框中的单词会以这样的方式出现在文本中。这是一个例子:“当您在处理复杂查询时,游乐场可以是一个有帮助的环境,底部的命令行部分甚至提供了您可以复制和在脚本中使用的命令行。”
提示或重要说明
显示如下。
联系我们
我们非常欢迎读者的反馈。
customercare@packtpub.com
。
勘误表:尽管我们已经尽一切努力确保内容的准确性,但错误是难免的。如果您在本书中发现错误,我们将非常感激您向我们报告。请访问www.packtpub.com/support/errata,选择您的书籍,点击“勘误提交表”链接,并输入详细信息。
copyright@packt.com
,附带材料的链接。
如果您有兴趣成为作者:如果您在某个专题上有专业知识,并且有兴趣撰写或为一本书做出贡献,请访问authors.packtpub.com。
评论
请留下评论。在阅读并使用本书后,为什么不在您购买它的网站上留下评论呢?潜在读者可以看到并使用您的公正意见来做出购买决策,我们在 Packt 可以了解您对我们产品的看法,我们的作者可以看到您对他们书籍的反馈。谢谢!
有关 Packt 的更多信息,请访问packt.com。
第一部分:介绍、安装和配置
通过本部分的学习,您将了解 Windows 子系统是什么,以及它与传统虚拟机的区别。您将能够安装 Windows 子系统并进行配置以满足您的需求。您还将能够安装新的 Windows 终端。
本节包括以下章节:
第一章,Windows 子系统介绍
第二章,安装和配置 Windows 子系统
第三章,使用 Windows 终端入门
第一章:Windows 子系统介绍 Linux
在本章中,您将了解到Windows 子系统 Linux(WSL)的一些用例,并开始对 WSL 的实际情况以及与仅运行 Linux 虚拟机相比的优劣有所了解。这将帮助我们理解本书的其余部分,我们将学习有关 WSL 的所有内容,以及如何安装和配置它,并获取有关如何在开发者工作流中充分利用它的技巧。
通过 WSL,您可以在 Windows 上运行 Linux 实用工具来帮助您完成工作。您可以使用原生 Linux 工具(如调试器)构建 Linux 应用程序,从而打开了一系列仅具有基于 Linux 的构建系统的项目。其中许多项目还会生成 Windows 二进制文件作为输出,但对于 Windows 开发人员来说,访问和贡献这些项目通常很困难。但由于 WSL 为您提供了 Windows 和 Linux 的综合功能,您可以做到这一切,并且仍然可以使用您喜爱的 Windows 实用工具作为工作流的一部分。
本书重点介绍 WSL 的第 2 版,这是一个重大改进的功能,本章将为您概述此版本的工作原理以及与第 1 版的比较。
在本章中,我们将特别涵盖以下主题:
-
什么是 WSL?
-
探索 WSL 1 和 2 之间的区别
所以,让我们从定义 WSL 开始!
什么是 WSL?
从高层次来看,WSL 提供了在 Windows 上运行 Linux 二进制文件的能力。多年来,人们一直希望能够运行 Linux 二进制文件,至少可以从Cygwin(cygwin.com
)等项目的存在来看。根据其主页的介绍,Cygwin 是“一个大型的 GNU 和开源工具集,提供类似于 Linux 发行版的功能”。在 Cygwin 上运行 Linux 应用程序需要重新构建源代码。WSL 提供了在 Windows 上运行 Linux 二进制文件的能力,无需修改。这意味着您可以立即获取您喜爱的应用程序的最新版本并与之一起工作。
希望在 Windows 上运行 Linux 应用程序的原因有很多,包括以下几点:
-
您目前正在使用 Windows,但对 Linux 应用程序和实用工具有经验和熟悉。
-
您在 Windows 上进行开发,但针对应用程序的部署目标是 Linux(直接或在容器中)。
-
您正在使用开发堆栈,其中生态系统在 Linux 上具有更强的存在,例如 Python,其中一些库是特定于 Linux 的。
无论您希望在 Windows 上运行 Linux 应用程序的原因是什么,WSL 都可以以一种新的、高效的方式为您提供这种能力。虽然在 Hyper-V 中运行 Linux虚拟机(VM)一直是可能的,但运行虚拟机会对您的工作流程产生一些障碍。
例如,启动虚拟机需要足够的时间,以至于您会中断思路,并且需要从主机机器中分配一定的内存。此外,虚拟机中的文件系统专用于该虚拟机,并与主机隔离。这意味着在 Windows 主机和 Linux 虚拟机之间访问文件需要设置 Hyper-V 功能的客户机集成服务或设置传统的网络文件共享。虚拟机的隔离还意味着虚拟机内部和外部的进程之间没有简单的通信方式。基本上,在任何时候,您要么在虚拟机中工作,要么在虚拟机外工作。
当您首次使用 WSL 启动终端时,您将在 Windows 上运行 Linux shell 的终端应用程序。与虚拟机体验相比,这个看似简单的差异已经更好地融入了工作流程,因为在同一台机器上的窗口之间切换比在 Windows 上的应用程序和虚拟机会话之间切换更容易。
然而,WSL 在集成 Windows 和 Linux 环境方面的工作还不止于此。虽然在虚拟机中,文件系统是被设计为隔离的,但在 WSL 中,默认情况下为你配置了文件系统访问。从 Windows 中,你可以访问一个名为\\wsl$\
的网络文件共享,当 WSL 运行时,它会自动为你提供访问你的 Linux 文件系统的权限。从 Linux 中,默认情况下会自动挂载你的本地 Windows 驱动器。例如,Windows 的C:
驱动器会被挂载为/mnt/c
。
更令人印象深刻的是,你可以在 Windows 中调用 Linux 中的进程,反之亦然。例如,在 WSL 的 Bash 脚本中,你可以调用一个 Windows 应用程序,并通过将其输出导入到另一个命令中在 Linux 中处理该应用程序的输出,就像你使用本地 Linux 应用程序一样。
这种集成超越了传统虚拟机所能实现的范围,并为将 Windows 和 Linux 的能力整合到一个单一的、高效的环境中创造了一些令人惊叹的机会,让你兼具两者的优势!
WSL 在 Windows 主机和 Linux 虚拟机环境之间实现的集成令人印象深刻。然而,如果你使用过 WSL 1 或熟悉它的工作原理,你可能已经阅读了前面的段落,并想知道为什么 WSL 2 放弃了之前的不使用虚拟机的架构。在接下来的部分中,我们将简要介绍 WSL 1 和 WSL 2 之间的不同架构,以及使用虚拟机带来的额外挑战,尽管 WSL 团队面临了创建我们刚刚看到的集成水平的难题。
探索 WSL 1 和 2 之间的差异
虽然本书讨论的是Windows 子系统 Linux(WSL 2)的第二个版本,但简要了解第一版(WSL 1)的工作原理是有帮助的。这将帮助你了解 WSL 1 的限制,并为 WSL 2 中的架构变化和新功能提供背景。本节将介绍这些内容,之后本书的其余部分将重点介绍 WSL 2。
WSL 1 概述
在 WSL 的第一个版本中,WSL 团队在 Linux 和 Windows 之间创建了一个翻译层。这个层在 Windows 内核之上实现了 Linux 系统调用,使得 Linux 二进制文件可以无需修改地运行;当 Linux 二进制文件运行并进行系统调用时,它调用的是 WSL 翻译层,并将其转换为对 Windows 内核的调用。如下图所示:
图 1.1 - 显示 WSL 1 翻译层的概述
除了翻译层之外,还进行了其他投资,以实现 Windows 和 WSL 之间的文件访问以及在两个系统之间调用二进制文件(包括捕获输出)的能力。这些能力有助于构建整体功能的丰富性。
在 WSL 1 中创建翻译层是一个大胆的举动,为 Windows 开辟了新的可能性,然而,并非所有的 Linux 系统调用都被实现,只有当所需的所有系统调用都被实现时,Linux 二进制文件才能运行。幸运的是,已经实现的系统调用可以让各种应用程序运行,例如 Python 和 Node.js。
翻译层负责弥合 Linux 和 Windows 内核之间的差距,这带来了一些挑战。在某些情况下,弥合这些差异会增加性能开销。在 WSL 1 上运行大量文件访问的应用程序明显较慢;例如,由于在 Linux 和 Windows 之间进行翻译的开销。
在其他情况下,Linux 和 Windows 之间的差异更深,很难看到如何调和它们。例如,在 Windows 上,当打开一个目录中包含的文件时尝试重命名该目录会导致错误,而在 Linux 上可以成功执行重命名操作。在这种情况下,很难看到翻译层如何解决差异。这导致一些系统调用未被实现,结果是一些 Linux 应用程序无法在 WSL 1 上运行。下一节将介绍 WSL 2 中所做的更改以及它们如何解决这个挑战。
WSL 2 概述
WSL 1 翻译层面虽然令人印象深刻,但它总是会面临性能挑战和难以正确实现的系统调用。通过 WSL 2,WSL 团队重新审视了问题,并提出了一个新的解决方案:一个虚拟机!这种方法通过运行 Linux 内核避免了 WSL 1 的翻译层:
图 1.2 - 显示 WSL 2 架构的概要
当你想到虚拟机时,你可能会想到启动速度慢(至少与启动 shell 提示符相比),启动时占用大量内存,并且与主机机器隔离运行的东西。从表面上看,使用虚拟化来运行 WSL 2 可能似乎出乎意料,因为在 WSL 1 中将这两个环境整合在一起的工作已经完成。实际上,在 Windows 上运行 Linux 虚拟机的能力早已存在。那么,WSL 2 与运行虚拟机有何不同?
使用文档中所称的轻量级实用虚拟机(参见docs.microsoft.com/en-us/windows/wsl/wsl2-about
),带来了很大的差异。这个虚拟机具有快速启动,只消耗少量内存。当运行需要内存的进程时,虚拟机会动态增加其内存使用量。更好的是,当虚拟机内的内存被释放时,它会返回给主机!
运行 WSL 2 的虚拟机意味着它现在正在运行 Linux 内核(其源代码可在github.com/microsoft/WSL2-Linux-Kernel
上获得)。这反过来意味着 WSL 1 翻译层面临的挑战被消除了:在 WSL 2 中,性能和系统调用兼容性都得到了极大改善。
WSL 2 对于大多数情况来说是向前迈出的积极一步,同时也保留了 WSL 1(Windows 和 Linux 之间的互操作性)的整体体验。
对于大多数用例来说,由于兼容性和性能,WSL 2 将是首选版本,但有几个值得注意的事项。其中之一是(在撰写本文时)WSL 2 的普遍可用版本不支持 GPU 或 USB 访问(详细信息请参见docs.microsoft.com/en-us/windows/wsl/wsl2-faq#can-i-access-the-gpu-in-wsl-2-are-there-plans-to-increase-hardware-support
)。GPU 支持在 2020 年 5 月的Build会议上宣布,并且在撰写本文时可通过 Windows Insiders 计划获得(insider.windows.com/en-us/
)。
另一个考虑因素是,由于 WSL 2 使用虚拟机,运行在 WSL 2 中的应用程序将通过与主机不同的网络适配器连接到网络(具有单独的 IP 地址)。正如我们将在第五章中看到的那样,WSL 团队在网络互操作性方面进行了投资,以帮助减少这种影响。
幸运的是,WSL 1 和 WSL 2 可以并行运行,因此如果您有特定的情况需要使用 WSL 1,您可以在那种情况下使用它,并且仍然可以在其他情况下使用 WSL 2。
总结
在本章中,您了解了 WSL 是什么以及它如何通过允许在 Windows 和 Linux 环境之间进行文件系统和进程集成来与传统虚拟机的体验有所不同。您还了解了 WSL 1 和 WSL 2 之间的区别概述以及为什么在大多数情况下,改进的性能和兼容性使得 WSL 2 成为首选选项。
在下一章中,您将学习如何安装和配置 WSL 和 Linux 发行版。
第二章:安装和配置 Windows 子系统 Linux
Windows 子系统 Linux(WSL)不是默认安装的,因此开始使用它的第一步将是安装它以及您选择的 Linux 发行版(distro)。通过本章的学习,您将了解如何安装 WSL 以及如何安装 Linux 发行版以供使用。您还将了解如何检查和控制 Linux 发行版,以及如何配置 WSL 中的其他属性。
在本章中,我们将特别介绍以下主要主题:
-
启用 WSL
-
在 WSL 中安装 Linux 发行版
-
配置和控制 WSL
启用 WSL
要准备好运行 WSL 的计算机,您需要确保您使用的是支持 WSL 的 Windows 版本。然后,您可以启用运行 WSL 所需的 Windows 功能,并安装 Linux 内核以准备安装 Linux 发行版。最后,您将能够安装一个或多个 Linux 发行版来运行。
让我们首先确保您正在使用最新版本的 Windows。
检查所需的 Windows 版本
要安装 WSL 2,您需要在运行的 Windows 10 版本上安装最新的版本。要检查您正在运行的 Windows 10 版本(以及是否需要更新),请按下Windows 键 + R,然后键入winver
:
图 2.1 - 显示 2004 更新的 Windows 版本对话框
在此屏幕截图中,您可以看到版本 2004表示系统正在运行 2004 版本。之后,您可以看到OS 构建为19041.208。
要运行 WSL 2,您需要使用版本 1903 或更高版本和 OS 构建 18362 或更高版本。(请注意,ARM64 系统需要使用版本 2004 或更高版本和 OS 构建 19041 或更高版本。)更多详细信息请参见docs.microsoft.com/en-us/windows/wsl/install-win10#requirements
。
如果您使用的是较低的版本号,请在计算机上转到Windows 更新并应用任何待处理的更新。
重要提示
Windows 10 更新的命名可能有点令人困惑,版本号如 1903 和 1909(或更糟糕的是,看起来像年份的 2004)的含义并不立即显而易见。命名是以yymm形式的年份和月份的组合,其中yy是年份的最后两位数字,mm是月份的两位数字形式。例如,1909 更新计划于 2019 年 9 月发布。同样,2004 版本计划于 2020 年 4 月发布。
现在您知道您使用的是所需的 Windows 版本,让我们开始启用 WSL。
检查是否有简易安装选项
在 2020 年 5 月的BUILD大会上,微软宣布了一种新的、简化的 WSL 安装方式,但在撰写本文时,这种新方法尚不可用。然而,由于这是一种快速简便的方法,您可能希望在使用较长的安装步骤之前尝试一下,以防在您阅读本文时已经可用!
要尝试一下,请打开您选择的提升的提示符(例如,命令提示符)并输入以下命令:
Wsl.exe --install
如果此命令运行,则表示您具有简易安装选项,并且它将为您安装 WSL。在这种情况下,您可以跳到配置和控制 WSL 部分(或者如果您想安装其他 Linux 发行版,则跳到安装 Linux 发行版在 WSL 中部分)。
如果找不到该命令,则继续下一节使用原始方法安装 WSL。
启用所需的 Windows 功能
正如在介绍章节中讨论的那样,WSL 的第二版使用了一种新的轻量级实用虚拟机功能。要启用轻量级虚拟机和 WSL,您需要启用两个 Windows 功能:虚拟机平台和Windows 子系统 Linux。
要通过“Windows 功能”启用这些功能,请单击如下图所示的打开或关闭 Windows 功能:
图 2.2-启动 Windows 功能选项
当 Windows 功能对话框出现时,请勾选虚拟机平台和Windows 子系统 Linux的复选框,如下图所示:
图 2.3-WSL 版本 2 所需的 Windows 功能
单击确定后,Windows 将下载并安装组件,并可能提示您重新启动计算机。
如果您喜欢通过命令行启用这些功能,请启动您选择的提升的提示符(例如,命令提示符)并输入以下命令:
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
完成这些命令后,重新启动计算机,您将准备好安装 Linux 内核。
安装 Linux 内核
在安装您喜欢的 Linux 发行版之前的最后一步是安装内核以便运行。在撰写本文时,这是一个手动步骤;将来计划通过 Windows 更新进行自动更新!
现在,访问aka.ms/wsl2kernel
获取下载和安装内核的链接。完成后,您可以选择要安装的Linux 发行版。
在 WSL 中安装 Linux 发行版
安装 WSL 的 Linux 发行版的标准方法是通过 Microsoft Store 进行。当前可用的 Linux 发行版的完整列表可以在官方文档中找到(docs.microsoft.com/windows/wsl/install-win10#install-your-linux-distribution-of-choice
)。在撰写本文时,这包括各种版本的 Ubuntu、OpenSUSE Leap、SUSE Linux Enterprise Server、Kali、Debian、Fedora Remix、Pengwin 和 Alpine。由于我们无法为本书中的每个 Linux 版本都提供示例,我们将重点介绍如何使用Ubuntu进行示例。
提示
前一章的步骤已经安装了运行版本 2 发行版所需的所有部分,但版本 1 仍然是默认设置!
这些命令将在本章的下一节中介绍,但如果您想将版本 2 设置为您安装的任何 Linux 发行版的默认设置,则运行以下命令:
wsl --set-default-version 2
如果您从 Windows 启动 Microsoft Store,可以搜索您选择的 Linux 发行版。例如,以下图显示了在 Microsoft Store 中搜索Ubuntu
的结果:
图 2.4-在 Microsoft Store 中搜索 Linux 发行版
当您找到想要的发行版时,请按照以下步骤进行操作:
-
单击它,然后单击安装。然后,商店应用程序将为您下载和安装发行版。
-
安装完成后,您可以点击启动按钮来运行。这将开始您选择的发行版的设置过程,如图所示(以 Ubuntu 为例)。
-
在设置过程中,您将被要求输入 UNIX 用户名(不必与 Windows 用户名匹配)和 UNIX 密码。
此时,您安装的发行版将运行 WSL 的版本 1(除非您之前运行过wsl --set-default-version 2
命令)。不用担心-下一节将介绍wsl
命令,包括在版本 1 和版本 2 之间转换已安装的 Linux 发行版!
现在您已经安装了 Linux 发行版,让我们来看看如何配置和控制它。
配置和控制 WSL
前面的部分简要提到了wsl
命令,这是与 WSL 交互和控制的最常见方式。在本节中,您将学习如何使用wsl
命令交互地控制 WSL,以及如何通过修改wsl.conf
配置文件中的设置来更改 WSL 的行为。
重要提示
早期版本的 WSL 提供了一个wslconfig.exe
实用程序。如果您在文档或文章中看到任何对此的引用,请不用担心-wslconfig.exe
的所有功能(以及更多)都可以在接下来的部分中看到的wsl
命令中使用。
以下部分中的命令和配置将为您提供控制 WSL 中运行的发行版以及配置发行版(以及整个 WSL)行为以满足您的要求所需的工具。
介绍 wsl 命令
wsl
命令提供了一种控制和与 WSL 及已安装的 Linux 发行版交互的方式,例如在发行版中运行命令或停止运行的发行版。在本节中,您将通过wsl
命令的最常用选项进行一次浏览。如果您感兴趣,可以通过运行wsl --help
找到完整的选项集。
列出发行版
wsl
命令是一个多功能命令行实用程序,既可以用于控制 WSL 中的 Linux 发行版,也可以用于在这些发行版中运行命令。
要开始,请运行wsl --list
以获取您已安装的 Linux 发行版的列表:
PS C:\> wsl --list
Windows Subsystem for Linux Distributions:
Ubuntu-20.04 (Default)
Legacy
docker-desktop
docker-desktop-data
WLinux
Alpine
Ubuntu
PS C:\>
前面的输出显示了已安装发行版的完整列表
,但还有一些其他开关可以应用于自定义此命令的行为。例如,如果您只想查看正在运行的发行版,则可以使用wsl --list --running
,如下面的片段所示:
PS C:\> wsl --list --running
Windows Subsystem for Linux Distributions:
Ubuntu-20.04 (Default)
Ubuntu
PS C:\>
列表命令的另一个有用变体是详细输出选项,使用wsl --list –verbose
来实现,如下所示:
PS C:\> wsl --list --verbose
NAME STATE VERSION
* Ubuntu-20.04 Running 2
Legacy Stopped 1
docker-desktop Stopped 2
docker-desktop-data Stopped 2
WLinux Stopped 1
Alpine Stopped 2
Ubuntu Running 2
PS C:\>
如前面的输出所示,详细选项显示了每个发行版使用的 WSL 版本;您可以看到同时支持1
和2
。详细输出还显示了每个发行版是否正在运行。它还在默认发行版旁边包含了一个星号(*
)。
除了获取有关 WSL 的信息外,我们还可以使用wsl
命令来控制发行版。
控制 WSL 发行版
如wsl --list --verbose
的输出所示,可以同时安装多个并且它们可以使用不同版本的 WSL。除了具有并行版本之外,安装后还可以在 WSL 版本之间转换发行版。要实现这一点,您可以使用wsl --set-version
命令。
此命令接受两个参数:
-
要更新的发行版的名称
-
要转换的版本
这里显示了将Ubuntu
发行版转换为版本 2 的示例:
PS C:\> wsl --set-version Ubuntu 2
Conversion in progress, this may take a few minutes...
For information on key differences with WSL 2 please visit https://aka.ms/wsl2
Conversion complete.
PS C:\>
默认情况下,为 WSL 安装 Linux 发行版将其安装为版本 1。但是,可以使用wsl --set-default-version
命令将其更改为默认版本,该命令接受一个版本参数作为默认版本。
例如,wsl --set-default-version 2
将使 WSL 的版本 2 成为您安装的任何新发行版的默认版本。
接下来,让我们来看看在 Linux 发行版中运行命令的方法。
使用 wsl 命令运行 Linux 命令
wsl
命令的另一个功能是在 Linux 中运行命令。实际上,如果不带任何参数运行wsl
,它将在默认发行版中启动一个 shell!
如果将命令字符串传递给wsl
,它将在默认发行版中运行该命令。例如,下面的片段显示了运行wsl ls ~
和wsl cat /etc/issue
的输出:
PS C:\> wsl ls ~
Desktop Downloads Pictures Templates source tmp
Documents Music Public Videos go ssh-test
PS C:\> wsl cat /etc/issue
Ubuntu 20.04 LTS \n \l
PS C:\>
从前面的 wsl cat /etc/issue
输出可以看出,命令是在 Ubuntu-20.04 发行版中运行的。如果您安装了多个发行版并且想要在特定的发行版中运行命令,则可以使用 -d
开关来指定要在其中运行命令的发行版。您可以使用 wsl --list
命令获取发行版名称。下面是一些 wsl -d
的示例:
PS C:\> wsl -d Ubuntu-20.04 cat /etc/issue
Ubuntu 20.04 LTS \n \l
PS C:\> wsl -d Alpine cat /etc/issue
Welcome to Alpine Linux 3.11
Kernel \r on an \m (\l)
PS C:\>
前面的示例显示了在多个发行版中运行 cat /etc/issue
命令,并且输出确认了命令所针对的发行版。
除了允许您选择在哪个 Linux 发行版中运行命令外,wsl
命令还允许您通过 -u
开关指定要以哪个用户身份运行命令。我发现最常用的用途是以 root 身份运行命令,这允许使用 sudo
在不提示输入密码的情况下运行命令。-u
开关在以下输出中进行了演示:
PS C:\> wsl whoami
stuart
PS C:\> wsl -u stuart whoami
stuart
PS C:\> wsl -u root whoami
root
PS C:\>
前面的输出显示了 whoami
命令(输出当前用户)。如果不传递 -u
开关,您可以看到命令是以在安装发行版时创建的 stuart
用户身份运行的,但是这可以被覆盖。
我们将看一个关于 wsl
命令停止运行发行版的最后一个示例。
使用 WSL 停止发行版
如果您一直在运行 WSL 并且想要出于任何原因停止它,也可以使用 wsl
命令来完成。
如果您运行了多个发行版,并且只想停止特定的一个,可以运行 wsl --terminate <distro>
,例如 wsl --terminate Ubuntu-20.04
。
提示
请记住,您可以使用 wsl --list --running
命令获取当前正在运行的发行版,就像我们之前看到的那样。
如果您想关闭 WSL 和所有正在运行的发行版,可以运行 wsl --shutdown
。
现在我们已经看到了如何使用 wsl
命令来控制 WSL,让我们来看看 WSL 的配置文件。
介绍 wsl.conf 和 .wslconfig
WSL 提供了几个可以配置其行为的位置。其中之一是 wsl.conf
,它提供了每个发行版的配置,另一个是 .wslconfig
,它提供了全局配置选项。这两个文件允许您启用 WSL 的不同功能,例如在发行版中挂载主机驱动器的位置,或者控制整体的 WSL 行为,例如它可以消耗多少系统内存。
使用 wsl.conf 进行工作
wsl.conf
文件位于每个发行版的 /etc/wsl.conf
文件中。如果该文件不存在,并且您想要对某个发行版应用一些设置,则在该发行版中创建带有所需配置的文件,并重新启动该发行版(参见“使用 WSL 停止发行版”部分中的 wsl --terminate
)。
默认选项通常工作良好,但本节将带您浏览 wsl.conf
,以便您了解如果需要,可以自定义哪些类型的设置。
wsl.conf
文件遵循 ini
文件结构,其中的名称/值对按部分组织。请参阅以下示例:
[section]
value1 = true
value2 = "some content"
# This is just a comment
[section2]
value1 = true
以下示例显示了 wsl.conf
文件的一些主要部分和值以及它们的默认选项:
[automount]
enabled = true # control host drive mounting (e.g. /mnt/c)
mountFsTab = true # process /etc/fstab for additional mounts
root = /mnt/ # control where drives are mounted
[interop]
enabled = true # allow WSl to launch Windows processes
appendWindowsPath = true # add Windows PATH to $PATH in WSL
automount
部分提供了控制 WSL 在发行版内部挂载 Windows 驱动器的选项。enabled
选项允许您完全启用或禁用此行为,而 root
选项允许您控制驱动器挂载应在发行版文件系统的哪个位置创建,如果您有理由或偏好将其放在不同的位置。
interop
部分控制着允许 Linux 发行版与 Windows 交互的功能。您可以通过将 enabled
属性设置为 false
来完全禁用该功能。默认情况下,Windows 的 PATH
会附加到发行版的 PATH
中,但如果您需要更精细地控制发现哪些 Windows 应用程序,则可以使用 appendWindowsPath
设置来禁用此功能。
有关wsl.conf
的完整文档可以在docs.microsoft.com/en-us/windows/wsl/wsl-config#configure-per-distro-launch-settings-with-wslconf
找到。您将在第五章中了解有关从 WSL 内部访问 Windows 文件和应用程序的更多信息,Linux 到 Windows 的互操作性。
在这里,我们已经看到了如何更改每个发行版的配置,接下来我们将看一下系统范围的 WSL 配置选项。
使用.wslconfig 文件
除了每个发行版的wsl.conf
配置外,WSL 的第 2 版还添加了一个全局的.wslconfig
文件,可以在您的Windows 用户
文件夹中找到,例如C:\Users\<您的用户名>\.wslconfig
。
与wsl.conf
文件一样,.wslconfig
使用ini
文件结构。以下示例显示了[wsl2]
部分的主要值,它允许您更改 WSL 版本 2 的行为:
[wsl2]
memory=4GB
processors=2
localhostForwarding=true
swap=6GB
swapFile=D:\\Temp\\WslSwap.vhdx
memory
值配置了用于 WSL 第 2 版的轻量级实用虚拟机消耗的内存限制。默认情况下,这是系统内存的 80%。
同样,processors
允许您限制虚拟机使用的处理器数量(默认情况下没有限制)。这两个值可以帮助您平衡在 Windows 和 Linux 上运行的工作负载。
另一个需要注意的是路径(例如swapFile
)需要是绝对路径,并且反斜杠(\\
)需要转义显示。
还有其他选项(例如kernel
和kernelCommandLine
),允许您指定自定义内核或其他内核参数,这超出了本书的范围,但可以在文档中找到:docs.microsoft.com/en-us/windows/wsl/wsl-config#configure-global-options-with-wslconfig
。
在本节中,您已经了解了如何通过在发行版的wsl.conf
文件中更改设置来控制 WSL 集成功能,例如驱动器挂载和调用 Windows 进程的能力。您还了解了如何控制整个 WSL 系统的行为,例如限制内存使用量或处理器数量。这些选项可以确保 WSL 以适合您的系统和工作流程的方式运行。
总结
在本章中,您已经了解了如何启用 WSL,安装 Linux 发行版,并确保它们在 WSL 的第 2 版下运行。您还学习了如何使用wsl
命令来控制 WSL,以及如何使用wsl.conf
和.wslconfig
配置文件进一步控制 WSL 和其中运行的发行版的行为。有了这些工具,您可以控制 WSL 及其与系统的交互方式。
在下一章中,我们将介绍新的 Windows 终端,它是与 WSL 自然配对的。我们将介绍如何安装它并使其运行起来。
第三章:开始使用 Windows 终端
微软已经宣布在即将发布的 Windows 子系统中支持 GUI 应用程序,但在撰写本书时,即使是早期预览形式也不可用。在本书中,我们选择关注 WSL 的稳定发布功能,因此它涵盖了 WSL 的当前以命令行为中心的视图。因此,装备一个良好的终端体验是有意义的。Windows 中的默认控制台体验(由cmd.exe
使用)在许多方面都有所欠缺,而新的 Windows 终端提供了许多好处。在本章中,我们将介绍其中一些好处,以及如何安装和开始使用 Windows 终端。
在本章中,我们将涵盖以下主要主题:
-
介绍 Windows 终端
-
安装 Windows 终端
-
使用 Windows 终端
-
配置 Windows 终端
介绍 Windows 终端
Windows 终端是 Windows 的替代终端体验。如果您习惯在 Windows 上运行命令行应用程序,您可能熟悉在运行 PowerShell 或cmd.exe
时看到的以前的 Windows 控制台体验(如下图所示):
图 3.1 - 显示 cmd.exe 用户体验的屏幕截图
Windows 控制台有着悠久的历史,可以追溯到 Windows NT 和 Windows 2000 时代,甚至可以追溯到 Windows 3.x 和 95/98 时代!在此期间,许多 Windows 用户创建了依赖于 Windows 控制台行为的脚本和工具。Windows 控制台团队设法改进了体验(例如,Ctrl +鼠标滚轮滚动以缩放文本,并改进了许多 Linux 和 UNIX 命令行应用程序和 shell 发出的 ANSI/VT 控制序列的处理),但在不破坏向后兼容性的情况下,他们最终受到了一些限制。
Windows 控制台团队花费了时间重构控制台的代码,以使其他终端体验(如新的 Windows 终端)能够在其之上构建。
新的 Windows 终端提供了许多改进,使其成为 Windows 控制台应用程序和 Linux shell 应用程序的终端体验更好。通过 Windows 终端,您可以更丰富地支持自定义终端的外观和感觉,并控制键绑定的配置。您还可以在终端中拥有多个选项卡,就像在 Web 浏览器中拥有多个选项卡一样,如下图所示:
图 3.2 - 显示 Windows 终端中多个选项卡的屏幕截图
除了每个窗口有多个选项卡外,Windows 终端还支持将选项卡分割为多个窗格。与选项卡不同,只有一个选项卡可见,而窗格可以将选项卡细分为多个部分。图 3.3显示了 Windows 终端中具有多个窗格的情况,其中混合了在 WSL2 中运行的 Bash 和在 Windows 中运行的 PowerShell:
图 3.3 - 显示 Windows 终端中多个窗格的屏幕截图
从上述屏幕截图中可以看出,与默认控制台体验相比,Windows 终端体验有了很大的改进。
您将学习如何利用其更丰富的功能,例如第六章中的窗格,从 Windows 终端获取更多信息,但现在您已经了解了 Windows 终端的特点,让我们开始安装吧!
安装 Windows 终端
Windows 终端(截至撰写本文时)仍在积极开发中,它位于 GitHub 上的github.com/microsoft/terminal
。如果您想运行最新的代码(或有兴趣贡献功能),那么 GitHub 上的文档将引导您完成构建代码所需的步骤。(GitHub 存储库也是提出问题和功能请求的好地方。)
安装 Windows 终端的更常见的方法是通过 Windows Store,它将安装应用程序并为您提供一种轻松的方式来保持更新。您可以在商店应用程序中搜索“Windows 终端”(如下图所示),或使用快速链接aka.ms/terminal
:
图 3.4-显示 Windows Store 应用程序的屏幕截图,显示 Windows 终端
如果您有兴趣提前测试功能(并且不介意潜在的偶尔不稳定),那么您可能会对 Windows 终端预览感兴趣。这也可以在商店应用程序中找到(您可能已经注意到它在前面的图中显示),或通过快速链接aka.ms/terminal-preview
获得。预览版本和主版本可以并行安装和运行。如果您对 Windows 终端的路线图感兴趣,可以在 GitHub 上的文档中找到github.com/microsoft/terminal/raw/master/doc/terminal-v2-roadmap.md
。
现在您已经安装了 Windows 终端,让我们来了解一些功能。
使用 Windows 终端
当您运行 Windows 终端时,它将启动您的默认配置文件。配置文件是指定在终端实例中运行哪个 shell 的一种方式,例如 PowerShell 或 Bash。单击标题栏中的+以使用默认配置文件创建一个新选项卡的另一个实例,或者您可以单击向下箭头选择要运行的配置文件,如下图所示:
图 3.5-显示用于创建新选项卡的配置文件下拉菜单的屏幕截图
前面的图显示了启动新终端选项卡的一系列选项,每个选项都被称为一个配置文件。显示的配置文件是由 Windows 终端自动生成的-它检测到我机器上安装了什么,并创建了动态配置文件列表。更好的是,如果我在安装 Windows 终端之后安装了新的 WSL 发行版,它将自动添加到可用配置文件列表中!我们稍后将快速查看如何配置您的配置文件,但首先,让我们看一些 Windows 终端的方便键盘快捷键。
学习方便的键盘快捷键
无论您是键盘快捷键的粉丝还是主要使用鼠标的用户,了解一些键盘快捷键都是有益的,尤其是对于 Windows 终端中的常见场景,因此本节列出了最常见的键盘快捷键。
您刚刚看到了如何使用 Windows 终端标题栏中的+和向下箭头启动具有默认配置文件的新选项卡或选择要启动的配置文件。使用键盘,可以使用Ctrl + Shift + T启动默认配置文件的新实例。要显示配置文件选择器,可以使用Ctrl + Shift +空格键,但是如果您查看图 3.5中的屏幕截图,您会看到前九个配置文件实际上有自己的快捷键:Ctrl + Shift + 1启动第一个配置文件,Ctrl + Shift + 2启动第二个配置文件,依此类推。
当您在 Windows 终端中打开多个标签页时,您可以使用Ctrl + Tab向前导航标签页,使用Ctrl + Shift + Tab向后导航(这与大多数带有标签的浏览器相同)。如果您想导航到特定的标签页,可以使用Ctrl + Alt +
使用键盘可以快速管理 Windows 终端中的标签页。如果 Windows 终端检测到很多配置文件,您可能希望控制它们的顺序,以便将您最常使用的配置文件放在顶部以便轻松访问(并确保它们获取快捷键)。我们将在下一节中看看这个以及其他一些配置选项。
配置 Windows 终端
Windows 终端的所有设置都存储在您的 Windows 配置文件中的一个JSON
文件中。要访问设置,您可以单击向下箭头选择要启动的配置文件,然后选择系统中JSON
文件的默认编辑器中的settings.json
。
settings
文件分为几个部分:
-
JSON
文件 -
每个配置文件的设置,独立定义和配置每个配置文件
-
指定配置文件可以使用的颜色方案的方案
-
键绑定,允许您自定义在 Windows 终端中执行任务的键盘快捷键
在 Windows 终端的设置中,有很多可以调整的选项,并且随着不断更新,会出现新的选项!所有设置的完整描述留给文档(docs.microsoft.com/en-us/windows/terminal/customize-settings/global-settings
),我们将重点关注一些可能要进行的自定义以及如何使用settings
文件实现它们。
让我们开始看一些您可能想要对 Windows 终端中的配置文件进行的自定义。
自定义配置文件
settings
文件的profiles
部分控制 Windows 终端在单击新标签下拉菜单时显示的配置文件,并允许您配置配置文件的各种显示选项。您还可以选择默认启动的配置文件,如下所示。
更改默认配置文件
您可能希望首先进行的更改之一是控制在启动 Windows 终端时默认启动哪个配置文件,以便自动启动您最常使用的配置文件。
此设置在全局设置中的defaultProfile
值中设置,如下例所示(全局设置是settings
文件顶层的值):
{
"$schema": "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "Ubuntu-20.04",
defaultProfile
设置的值允许您使用要设置为默认配置文件的配置文件的name
(或关联的guid
)属性。请确保输入与profiles
部分中指定的名称完全相同。
接下来,您将查看如何更改 Windows 终端配置文件的顺序。
更改配置文件的顺序
您可能希望进行的另一个提高生产力的更改是按照最常用的配置文件顺序排列,以便轻松访问顶部。如果您使用键盘快捷键启动新标签页,则顺序决定了快捷键是什么,因此在这里顺序具有额外的重要性。以下图显示了在我的机器上的初始顺序,如前一节中的设置所示:
图 3.6 - 显示初始配置文件顺序的屏幕截图
在屏幕截图中,您可以看到 PowerShell 是第一个列出的配置文件(您还可以注意到 PowerShell 以粗体显示,表示它是默认配置文件)。
要更改 UI 中配置文件的顺序,我们可以更改settings
文件中profiles
下的list
中的条目顺序。以下代码片段显示了上一节中的设置更新,使Ubuntu-20.04成为列表中的第一项:
"profiles":
{
"defaults":
{
// Put settings here that you want to apply to all profiles.
},
"list":
{
"guid": "{07b52e3e-de2c-5db4-bd2d-ba144ed6c273}",
"hidden": false,
"name": "Ubuntu-20.04",
"source": "Windows.Terminal.Wsl"
},
{
"guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
"hidden": false,
"name": "PowerShell",
"source": "Windows.Terminal.PowershellCore"
},
{
"guid": "{6e9fa4d2-a4aa-562d-b1fa-0789dc1f83d7}",
"hidden": false,
"name": "Legacy",
"source": "Windows.Terminal.Wsl"
},
// ... more settings omitted
保存settings
文件后,您可以返回到 Windows 终端的下拉菜单中查看顺序的更改:
![图 3.7 - 显示更新后的配置文件顺序的屏幕截图图 3.7 - 显示更新后的配置文件顺序的屏幕截图在上述屏幕截图中,请注意Ubuntu-20.04位于列表顶部,现在具有Ctrl+Shift+1的快捷键。值得注意的是,PowerShell仍然以粗体显示,表示它仍然是默认配置文件,即使它不再是列表中的第一个。需要注意的一点是,列表中的每个项目都需要用逗号分隔,并且最后一个列表项后面不能有逗号。如果您更改列表末尾的项目,这可能会让您感到困惑。然而,Windows 终端可能会显示警告,如下图所示:
图 3.8 - 显示加载设置时出现错误的示例屏幕截图
如果您在上述屏幕截图中看到错误,请不要担心。当 Windows 终端运行时,它会在文件更改时重新加载设置。错误指出了settings
文件中有错误的部分。当您关闭错误时,Windows 终端仍会重新加载您的设置。
除了控制配置文件在列表中显示的顺序,您还可以更改它们在列表中的显示方式,如下所示。
重命名配置文件和更改图标
Windows 终端在预填充配置文件方面做得很好,但您可能希望重命名配置文件。要做到这一点,请根据以下代码片段所示,更改相关配置文件的name
属性的值。与之前一样,一旦保存文件,Windows 终端将重新加载它并应用更改:
{
"guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
"hidden": false,
"name": "** PowerShell **",
"source": "Windows.Terminal.PowershellCore"
},
您甚至可以通过 Windows 表情符号支持进一步操作。当您更改配置文件的名称时,按下Win + .以打开表情符号选择器,然后继续输入以过滤表情符号列表。例如,下图显示了筛选到猫的情况:
图 3.9 - 显示使用表情选择器的屏幕截图
从列表中选择一个表情符号将其插入到编辑器中,如下图所示:
图 3.10 - 显示已完成的 PowerShell 配置文件的屏幕截图
在此屏幕截图中,您可以看到在name
属性中使用了一个表情符号。除了更改名称外,设置还允许您自定义列表中配置文件旁边显示的图标。通过向配置文件添加一个图标属性来实现,该属性给出了您希望使用的图标的路径,如上一个屏幕截图所示。该图标可以是PNG
,JPG
,ICO
或其他文件类型 - 我倾向于使用PNG
,因为它在各种编辑器中易于使用,并允许图像的透明部分。
值得注意的是,路径需要将反斜杠(\
)转义为双反斜杠(\\
)。方便的是,您还可以在路径中使用环境变量。这使您可以将图标放在 OneDrive(或其他文件同步平台)中,并在多台机器上共享它们(或仅备份以供将来使用)。要使用环境变量,请将其用百分号括起来,如上面的代码片段中所示的%OneDrive%
。
这些自定义(图标和文本)的结果如下图所示:
图 3.11 - 显示自定义图标和文本(包括表情符号!)的屏幕截图
到目前为止,您已经了解了如何控制配置文件列表中的项目以及它们的显示方式。最后要看的是如何从列表中删除项目。
删除配置文件
如果您已经阅读了前面的部分,您可能会认为删除配置文件只需从列表中删除条目即可。然而,如果配置文件是动态生成的,则在下次加载设置时,Windows Terminal 将重新添加该配置文件(在列表底部)!虽然这可能看起来有点奇怪,但这是 Windows Terminal 自动检测新配置文件(例如新的 WSL Distros)的副作用,即使您在安装 Windows Terminal 之后安装它们。相反,要防止配置文件显示在列表中,您可以设置隐藏属性,如下面的代码片段所示:
{
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"name": "Command Prompt",
"commandline": "cmd.exe",
"hidden": true
}
现在我们已经探索了如何控制 Windows Terminal 中的配置文件,让我们来看看如何自定义其外观。
更改 Windows Terminal 的外观
Windows Terminal 提供了多种方式来自定义其外观,您进行这些操作的动机可能纯粹是为了美观,也可能是为了通过增大字体大小、增加对比度或使用特定字体使内容更易读(例如,在www.opendyslexic.org/
上提供的OpenDyslexic字体)来使终端更易于使用。
更改字体
Windows Terminal 的默认字体是一种名为!=
的新字体,当呈现为≠
时,这两个字符会合并在一起。如果您不想使用连字,则Cascadia Mono是相同的字体,但不包含连字。
可以通过在配置文件中设置fontFace
和fontSize
属性来独立更改每个配置文件的字体,如下面的示例所示:
{
"guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
"hidden": false,
"name": "PowerShell",
"source": "Windows.Terminal.PowershellCore",
"fontFace": "OpenDyslexicMono",
"fontSize": 16
},
如果您想为所有配置文件自定义字体设置,可以在defaults
部分中添加fontFace
和fontSize
属性,如下面的代码片段所示:
"profiles": {
"defaults": {
// Put settings here that you want to apply to all profiles.
"fontFace": "OpenDyslexicMono",
"fontSize": 16
},
在defaults
部分指定的设置适用于所有配置文件,除非配置文件覆盖它。现在我们已经了解了如何更改字体,让我们来看看如何控制颜色方案。
更改颜色
Windows Terminal 允许您以几种方式自定义配置文件的颜色方案。
最简单的自定义是在配置文件中使用foreground
、background
和cursorColor
属性。这些值以#rgb
或#rrggbb
的形式指定为 RGB 值(例如,#FF0000
表示亮红色)。以下是示例代码片段:
{
"guid": "{07b52e3e-de2c-5db4-bd2d-ba144ed6c273}",
"name": "Ubuntu-20.04",
"source": "Windows.Terminal.Wsl",
"background": "#300A24",
"foreground": "#FFFFFF",
"cursorColor": "#FFFFFF"
},
要更精细地控制颜色,您可以在settings
文件的schemes
部分下创建一个颜色方案。有关详细信息,请参阅docs.microsoft.com/en-us/windows/terminal/customize-settings/color-schemes
,其中包括内置颜色方案的列表。如下面的示例所示,方案具有名称和一组以#rgb
或#rrggbb
形式的颜色规范:
"schemes": [
{
"name" : "Ubuntu-inspired",
"background" : "#300A24",
"foreground" : "#FFFFFF",
"black" : "#2E3436",
"blue" : "#0037DA",
"brightBlack" : "#767676",
"brightBlue" : "#3B78FF",
"brightCyan" : "#61D6D6",
"brightGreen" : "#16C60C",
"brightPurple" : "#B4009E",
"brightRed" : "#E74856",
"brightWhite" : "#F2F2F2",
"brightYellow" : "#F9F1A5",
"cyan" : "#3A96DD",
"green" : "#13A10E",
"purple" : "#881798",
"red" : "#C50F1F",
"white" : "#CCCCCC",
"yellow" : "#C19C00"
}
],
定义颜色方案后,您需要更新配置文件设置以使用它。您可以使用colorScheme
属性指定这一点,并将其应用于单个配置文件级别,或者使用前面在本章中看到的default
部分将其应用于所有配置文件。以下是将其应用于单个配置文件的示例:
{
"guid": "{07b52e3e-de2c-5db4-bd2d-ba144ed6c273}",
"name": "Ubuntu-20.04",
"source": "Windows.Terminal.Wsl",
"colorScheme": "Ubuntu-inspired"
},
保存这些更改后,Windows Terminal 将将您定义的颜色方案应用于使用该配置文件的任何选项卡。
通过这里展示的选项,您可以自定义默认启动的配置文件以及配置文件在配置文件列表中的显示顺序和方式。您已经看到了各种选项,可以让您自定义配置文件在运行时的显示方式,这将使您能够轻松应用其他设置,如设置背景图像或更改终端配置文件的透明度。完整的详细信息可以在 Windows 终端文档中找到:docs.microsoft.com/en-us/windows/terminal/customize-settings/profile-settings
。
总结
在本章中,您已经了解了 Windows 终端以及它如何通过更好地控制显示和支持多个选项卡等功能来改进以前的终端体验。在使用 WSL 时,自动检测您安装的新 Linux 发行版的终端也是一个不错的好处!
您已经了解了如何安装和使用 Windows 终端,以及如何根据自己的喜好进行自定义,以便您可以轻松阅读文本,并定义颜色方案以便轻松知道哪些终端配置文件正在运行。通过自定义默认配置文件和配置文件顺序,您可以确保轻松访问您最常使用的配置文件,帮助您保持高效。在下一章中,我们将开始使用 Windows 终端,探索如何在 Windows 上与 Linux 发行版进行交互。
第二部分:Windows 和 Linux - 一个胜利的组合
本节深入探讨了在 Windows 和 Windows 子系统之间进行工作的一些奇妙之处,展示了这两个操作系统如何协同工作。您还将了解更多关于有效使用 Windows 终端的技巧。最后,您还将了解如何在 WSL 中使用容器以及如何复制和管理您的 WSL 发行版。
本节包括以下章节:
【第四章】,Windows 与 Linux 的互操作性
【第五章】,Linux 到 Windows 的互操作性
【第六章】,从 Windows 终端获取更多功能
【第七章】,在 WSL 中使用容器
【第八章】,使用 WSL 发行版进行工作
第四章:Windows 与 Linux 的互操作性
在第一章中,我们将 WSL 体验与在虚拟机中运行 Linux 进行了比较;虚拟机专注于隔离,而 WSL 在 Windows 和 Linux 之间具有强大的互操作性。在本章中,您将开始了解这些功能,从与在 WSL 下运行的文件和应用程序进行交互开始,以及从 Windows 主机环境访问文件。这将包括查看如何在 Windows 和 WSL 中的脚本之间传递输出。之后,我们将看看 WSL 如何使 Linux 中的 Web 应用程序可以从 Windows 访问。
在本章中,我们将介绍以下主要内容:
-
从 Windows 访问 Linux 文件
-
从 Windows 运行 Linux 应用程序
-
从 Windows 访问 Linux Web 应用程序
让我们开始吧!
从 Windows 访问 Linux 文件
当您安装了 WSL 后,您会得到一个新的\\wsl$
路径,您可以在 Windows 资源管理器和其他程序中使用它。如果您在 Windows 资源管理器的地址栏中键入\\wsl$
,它将列出任何正在运行的 Linux 发行版(distros),如下面的截图所示:
图 4.1 - 在 Windows 资源管理器中显示\wls$的截图
图 4.1 - 在 Windows 资源管理器中显示的\wls$的截图
如前面的截图所示,每个正在运行的发行版都显示为\\wsl$
下的路径。每个\\wsl$\<distroname>
都是访问<distroname>
文件系统根目录的 Windows 路径。例如,\\wsl$\Ubuntu-20.04
是从 Windows 访问Ubuntu-20.04
发行版文件系统根目录的路径。这是一种非常灵活和强大的功能,使您可以完全访问 Windows 上的 Linux 发行版的文件系统。
下面的截图显示了 Windows 资源管理器中的\\wsl$\Ubuntu-20.04\home\stuart\tmp
路径。这对应于Ubuntu-20.04
发行版中的~/tmp
文件夹:
图 4.2 - 在 Windows 资源管理器中显示 Linux 发行版内容的截图
图 4.2 - 在 Windows 资源管理器中显示 Linux 发行版内容的截图
在这些截图中,您可以在 Windows 资源管理器中看到 Linux 文件系统,但是任何可以接受 UNC 路径(即以\\
开头的路径)的应用程序都可以使用这些路径。例如,从 PowerShell 中,您可以像在 Windows 中一样读取和写入 Linux 文件系统:
C:\ > Get-Content '\\wsl$\ubuntu-20.04\home\stuart\tmp\hello-wsl.txt'
Hello from WSL!
C:\ >
在此示例中,在 Ubuntu 20.04 发行版中创建了一个名为~/tmp/hello-wsl.txt
的文本文件,内容为Hello from WSL!
,并使用Get-Content
PowerShell cmdlet 使用我们之前看到的\\wsl$\...
路径读取文件的内容。
在 Windows 资源管理器中浏览文件系统时,双击文件将尝试在 Windows 中打开它。例如,双击我们在图 4.2中查看的文本文件将在您的默认文本编辑器(在我的情况下是记事本)中打开,如下面的截图所示:
图 4.3 - 在记事本中打开的 Linux 文件的截图
图 4.3 - 在记事本中打开的 Linux 文件的截图
此截图显示了与之前通过 PowerShell 获取文件内容的示例相同的内容,但在记事本中打开。使用\\wsl$\...
路径。
提示
如果您浏览到\\wsl$
并且看不到您安装的发行版之一,则表示该发行版未运行。
启动发行版的简单方法是使用 Windows 终端在其中启动一个 shell。或者,如果您知道发行版的名称,您可以在 Windows 资源管理器的地址栏(或您正在使用的任何应用程序)中键入\\wsl$\<distroname>
,WSL 将自动启动发行版,以允许您浏览文件系统!
正如您在本节中所看到的,\\wsl$\
共享提供了从 Windows 应用程序访问 WSL 发行版文件系统内文件的能力。这是在 WSL 中桥接 Windows 和 Linux 的有用步骤,因为它允许您使用 Windows 工具和应用程序来处理 Linux 文件系统中的文件。
接下来,我们将看一下如何从 Windows 中运行 WSL 中的应用程序。
从 Windows 中运行 Linux 应用程序
在第二章中,安装和配置 Windows 子系统,我们简要介绍了wsl
命令,并且您看到了它如何用于控制运行的发行版和在发行版内执行应用程序。在本节中,我们将深入探讨使用wsl
命令在发行版中运行应用程序。
正如我们在上一节中看到的,能够在 Windows 和 Linux 之间访问文件非常有用,而能够调用应用程序进一步增强了这一功能。WSL 不仅可以从 Windows 中的发行版运行应用程序,还可以在应用程序之间进行输出导入。在 Windows 或 Linux 中构建脚本时,通过应用程序之间的输出导入来构建脚本功能是一种非常常见的方式。能够在 Windows 和 Linux 命令之间进行输出导入,使您能够构建在 Windows 和 Linux 上运行的脚本,这真的有助于建立这两个环境的统一感。我们将开始看一下它是如何工作的。
导入到 Linux 中
在本节中,我们将探讨将数据从 Linux 传输到 Windows 的方法。我遇到过很多次的一种情况是有一些数据,比如日志输出,我想对其进行一些处理。一个例子是处理每一行以提取 HTTP 状态码,然后进行分组和计数,以计算记录了多少个成功和失败。我们将使用一个代表这种情况的示例,但不需要任何真实的设置:我们将检查 Windows 目录中以每个字母开头的文件数量。
让我们从一些 PowerShell 开始(我们将逐步构建脚本,所以如果您对 PowerShell 不太熟悉,不用担心):
- 首先,我们将使用
Get-ChildItem
获取Windows
文件夹的内容,如下所示:
SystemRoot environment variable to refer to the Windows folder (typically C:\Windows) in case you have customized the install location. The output shows some of the files and folders from the Windows folder, and you can see various properties for each item, such as LastWriteTime, Length, and Name.
- 接下来,我们可以执行提取操作,例如提取文件名的第一个字母。我们可以通过将
Get-ChildItem
的输出导入到ForEach-Object
cmdlet 中来扩展我们之前的命令,如下所示:
ForEach-Object, which takes the input ($_) and gets the first character using Substring, which lets you take part of a string. The first argument to Substring specifies where to start (0 indicates the start of the string) and the second argument is how many characters to take. The previous output shows that some of the files and folders start with lowercase and others start with uppercase, so we call ToUpper to standardize using uppercase.
- 下一步是对项目进行分组和计数。由于目标是演示在 Windows 和 Linux 之间进行输出导入,我们暂时忽略 PowerShell 的
Group-Object
cmdlet,而是使用一些常见的 Linux 实用工具:sort
和uniq
。如果您在 Linux 中使用这些命令与其他输出一起使用,可以将其作为other-command | sort | uniq -c
进行管道传输。然而,由于sort
和uniq
是 Linux 命令,我们在 Windows 上运行此命令,需要使用wsl
命令来运行它们,如下面的输出所示:
PS C:\> Get-Childitem $env:SystemRoot | ForEach-Object { $_.Name.Substring(0,1).ToUpper() } | wsl sort | wsl uniq -c
5 A
5 B
5 C
9 D
3 E
2 F
...
前面的输出显示了我们的目标结果:每个字母开头的文件和文件夹的数量。但更重要的是,它显示了将 Windows 命令的输出导入 Linux 命令的管道工作正常!
在此示例中,我们调用了两次wsl
:一次用于sort
,一次用于uniq
,这将导致输出在管道中的每个阶段在 Windows 和 Linux 之间进行传输。如果我们稍微改变命令的结构,我们可以使用单个wsl
调用。尝试将输入管道到wsl sort | uniq -c
可能会很诱人,但这会尝试将wsl sort
的输出管道到 Windows 的uniq
命令中。您还可以考虑wsl "sort | uniq -c"
,但会出现错误/bin/bash: sort | uniq -c: command not found
。相反,我们可以使用wsl
运行bash
和我们的命令wsl bash -c "sort | uniq -c"
。完整的命令如下:
PS C:\> Get-Childitem $env:SystemRoot | ForEach-Object { $_.Name.Substring(0,1).ToUpper() } | wsl bash -c "sort | uniq -c"
5 A
5 B
5 C
9 D
3 E
2 F
...
正如您所看到的,这与先前版本的输出相同,但只执行了一次wsl
。虽然这可能不是运行复杂命令的最明显的方法,但它是一种有用的技术。
在这个例子中,我们一直关注将数据导入 Linux,但当从 Linux 命令导出输出时,它同样有效,我们将在下一节中看到。
从 Linux 进行管道传输
在前一节中,我们看了如何将 Windows 命令的输出导入 Linux,并通过使用 PowerShell 检索Windows
文件夹中的项目并获取它们的首字母,然后将字母传递给 Linux 实用程序进行排序、分组和计数来探索这一点。在本节中,我们将看看如何将 Linux 实用程序的输出导入 Windows。我们将使用反向示例,通过 Bash 列出文件并使用 Windows 实用程序处理输出。
首先,让我们从默认的发行版中获取/usr/bin
文件夹中的文件和文件夹:
PS C:\> wsl ls /usr/bin
2to3-2.7 padsp
GET pager
HEAD pamon
JSONStream paperconf
NF paplay
POST parec
Thunar parecord
...
此输出显示了/usr/bin
文件夹的内容,下一步是获取名称的第一个字符。为此,我们可以使用cut
命令。我们可以运行wsl ls /usr/bin | wsl cut -c1
,但我们可以重用我们在上一节中看到的技术将其组合成一个单独的wsl
命令:
PS C:\> wsl bash -c "ls /usr/bin | cut -c1"
2
G
H
J
N
P
T
从前面的输出中可以看到,我们现在只有第一个字符,并且我们已经准备好对它们进行排序和分组。为了进行这个练习,我们假装sort
和uniq
命令不存在,而是使用 PowerShell 的Group-Object
cmdlet:
PS C:\> wsl bash -c "ls /usr/bin | cut -c1-1" | Group-Object
Count Name Group
----- ---- -----
1 [ {[}
1 2 {2}
46 a {a, a, a, a…}
79 b {b, b, b, b…}
82 c {c, c, c, c…}
79 d {d, d, d, d…}
28 e {e, e, e, e…}
49 f {f, f, f, f…}
122 G {G, g, g, g…}
在这里,我们可以看到从在 WSL 中运行的 Bash 命令成功地通过管道传输到 PowerShell 的Group-Object
cmdlet。在前一节中,我们强制将字符转换为大写,但在这里我们不需要这样做,因为Group-Object
默认执行不区分大小写的匹配(尽管可以使用-CaseSensitive
开关覆盖这一点)。
通过这些示例,您可以通过 WSL 调用 Linux 发行版来执行 Linux 应用程序和实用程序。这些示例只使用了默认的 WSL 发行版,但在上面的所有示例中,您可以在wsl
命令上添加-d
开关以指定要在其中运行 Linux 命令的发行版。如果您有多个发行版,并且您需要的特定应用程序仅在其中一个发行版中可用,这将非常有用。
能够在 Windows 和 Linux 应用程序之间双向传输输出允许在组合应用程序时具有很大的灵活性。如果您更熟悉 Windows 实用程序,您可以执行 Linux 应用程序,然后使用 Windows 实用程序处理结果。或者,如果 Linux 是您更熟悉的地方,但您需要在 Windows 机器上工作,那么能够调用熟悉的 Linux 实用程序来处理 Windows 输出将帮助您提高工作效率。
您已经看到如何从 Windows 访问 Linux 文件并从 Windows 调用 Linux 应用程序。在下一节中,您将看到如何从 Windows 访问在 WSL 中运行的 Web 应用程序。
从 Windows 访问 Linux Web 应用程序
如果您正在开发 Web 应用程序,那么在您工作时,通常会在 Web 浏览器中打开应用程序,地址为http://localhost
。使用 WSL,您的 Web 应用程序在 WSL 轻量级虚拟机内运行,该虚拟机具有单独的 IP 地址(您可以使用 Linux 的ip addr
命令找到此地址)。幸运的是,WSL 将 localhost 地址转发到 Linux 发行版以保持自然的工作流程。您将在本节中了解到这一点。
要跟随本章的内容,请确保您已经在 Linux 发行版中克隆了本书的代码,在终端中打开,并导航到chapter-04/web-app
文件夹,网址为github.com/PacktPublishing/Windows-Subsystem-for-Linux-2-WSL-2-Tips-Tricks-and-Techniques/tree/main/chapter-04
。
示例代码使用 Python 3,如果您使用的是最新版本的 Ubuntu,则应该已经安装了 Python 3。您可以通过在 Linux 发行版中运行python3 -c 'print("hello")'
来测试是否安装了 Python 3。如果命令成功完成,则说明已经准备就绪。如果没有,请参考 Python 文档以获取安装说明:wiki.python.org/moin/BeginnersGuide/Download
。
在chapter-04/web-app
文件夹中,您应该看到index.html
和run.sh
。在终端中运行./run.sh
来运行 Web 服务器:
$ ./run.sh
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
您应该看到类似于前面输出的输出,以指示 Web 服务器正在运行。
您可以通过在 Linux 发行版中启动新的终端并运行curl
来验证 Web 服务器是否正在运行:
$ curl localhost:8080
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chapter 4</title>
</head>
<body>
<h1>Hello from WSL</h1>
<p>This content is brought to you by python <a href="https://docs.python.org/3/library/http.server.html">http.server</a> from WSL.</p>
</body>
</html>
$
此输出显示了 Web 服务器对curl
请求的响应返回的 HTML。
接下来,在 Windows 中打开您的 Web 浏览器,并导航到http://localhost:8080
。
图 4.4 - 显示 WSL Web 应用程序在 Windows 浏览器中的屏幕截图
正如前面的屏幕截图所示,WSL 将 Windows 中的localhost流量转发到 Linux 发行版。当您使用 WSL 开发 Web 应用程序或运行具有 Web 用户界面的应用程序时,您可以使用localhost访问 Web 应用程序,就像它在 Windows 本地运行一样;这是另一种真正平滑用户体验的集成。
总结
在本章中,您已经看到了 WSL 允许我们如何与 Linux 发行版在 Windows 中进行互操作的方式,从通过\\wsl$\...
路径访问 Linux 文件系统开始。您还看到了如何从 Windows 调用 Linux 应用程序,并且可以通过在它们之间传递输出来链接 Windows 和 Linux 命令,就像在任一系统中一样。最后,您看到了 WSL 将localhost请求转发到在 WSL 发行版内部运行的 Web 服务器。这使您可以轻松地在 WSL 中开发和运行 Web 应用程序,并从 Windows 浏览器中进行测试。
能够从 Windows 访问 WSL 发行版的文件系统并在其中执行命令,真正有助于将这两个系统结合在一起,并帮助您选择您喜欢的工具来完成您正在进行的任务,而不管它们在哪个操作系统中。在下一章中,我们将探索从 WSL 发行版内部与 Windows 交互的能力。
第五章:Linux 到 Windows 的互操作性
在第一章中,介绍 Windows 子系统 Linux,我们将 WSL 体验与在虚拟机中运行 Linux 进行了比较,并提到了 WSL 的互操作性能力。在第四章中,Windows 到 Linux 的互操作性,我们看到了如何开始利用这些互操作性功能。在本章中,我们将继续探索互操作性功能,但这次是从 Linux 端进行。这将使您能够将 Windows 命令和工具的功能带入 WSL 环境中。
我们将首先看一下如何在 WSL 环境中与 Windows 应用程序和文件进行交互。接下来,我们将介绍如何在 Linux 和 Windows 之间处理脚本,包括如何在它们之间传递输入。最后,我们将提供一些互操作性技巧和窍门,以提高您的生产力,从通过别名使 Windows 命令更加自然,到在 Windows 和 Linux 之间共享安全外壳(SSH)密钥以便于使用和维护。
在本章中,我们将涵盖以下主要主题:
-
从 Linux 访问 Windows 文件
-
从 Linux 调用 Windows 应用程序
-
从 Linux 调用 Windows 脚本
-
互操作性技巧和窍门
让我们从第一个主题开始!
从 Linux 访问 Windows 文件
默认情况下,WSL 会自动将 Windows 驱动器挂载到 WSL 的/mnt
目录下;例如,您的C:
驱动器会被挂载为/mnt/c
。要尝试这个功能,请在C:
驱动器上创建一个名为wsl-book
的文件夹,并在其中放置一个example.txt
文件(文本文件的内容并不重要)。现在,在 WSL 中打开一个终端并运行ls /mnt/c/wsl-book
,您将在 Bash 输出中看到您创建的文件:
图 5.1 - 屏幕截图显示了从 Windows 和 WSL 列出文件夹内容
此屏幕截图显示了 Windows 中的目录列表,右侧是 WSL 发行版中/mnt/c
路径下的example.txt
。
您可以像与任何其他文件一样与挂载的文件进行交互;例如,您可以使用cat
命令查看文件的内容:
$ cat /mnt/c/wsl-book/example.txt
Hello from a Windows file!
或者,您可以将内容重定向到 Windows 文件系统中的文件:
$ echo "Hello from WSL" > /mnt/c/wsl-book/wsl.txt
$ cat /mnt/c/wsl-book/wsl.txt
Hello from WSL
或者,您可以在vi
(或您喜欢的其他终端文本编辑器)中编辑文件:
图 5.2 - 屏幕截图显示了在 WSL 下使用 vi 编辑 Windows 文件
在此屏幕截图中,您可以看到从 Windows 文件系统中的文件在 WSL 发行版中的vi
中进行编辑,之前运行了vi /mnt/c/wsl-book/wsl.txt
命令。
重要提示
在 Windows 下,文件系统通常是不区分大小写的;也就是说,Windows 将SomeFile
视为与somefile
相同。在 Linux 下,文件系统是区分大小写的,因此它们将被视为两个不同的文件。
当从 WSL 挂载访问 Windows 文件系统时,Linux 端对文件进行区分大小写处理,因此尝试从/mnt/c/wsl-book/EXAMPLE.txt
读取将失败。
尽管 Linux 端将文件系统视为区分大小写,但底层的 Windows 文件系统仍然是不区分大小写的,这一点很重要。例如,虽然 Linux 会将/mnt/c/wsl-book/wsl.txt
和/mnt/c/wsl-book/WSL.txt
视为不同的文件,但从 Linux 写入/mnt/c/wsl-book/WSL.txt
实际上会覆盖先前创建的wsl.txt
文件的内容,因为 Windows 将名称视为不区分大小写。
正如您在本节中所看到的,自动创建的挂载点(/mnt/...
)使得通过 WSL 非常容易访问 Windows 文件(如果您想禁用此挂载或更改挂载点的位置,可以使用wsl.conf
,如第二章所示,安装和配置 Windows 子系统用于 Linux)。下一节将介绍如何从 Linux 调用 Windows 应用程序。
从 Linux 调用 Windows 应用程序
在第四章中,我们看到了如何使用wsl
命令从 Windows 调用 Linux 应用程序。而从 Linux 调用 Windows 应用程序则更加简单!为了看到这一点,启动 WSL 发行版中的终端,并运行/mnt/c/Windows/System32/calc.exe
来直接从 Linux 启动 Windows 计算器应用程序。如果 Windows 没有安装在C:\Windows
中,则更新路径以匹配。通过这种方式,您可以从 WSL 发行版的终端启动任何 Windows 应用程序。
在 Windows 计算器(以及许多其他应用程序)的情况下,WSL 实际上使得这更容易。这次,在终端中键入calc.exe
,Windows 计算器仍然会运行。之所以能够运行,是因为calc.exe
在 Windows 路径中,并且(默认情况下)WSL 将映射 Windows 路径到 WSL 发行版中的 Linux 路径。为了证明这一点,在终端中运行echo $PATH
:
$ echo $PATH
/home/stuart/.local/bin:/home/stuart/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/mnt/c/Program Files (x86)/Microsoft SDKs/Azure/CLI2/wbin:/mnt/c/WINDOWS/system32:/mnt/c/WINDOWS:/mnt/c/WINDOWS/System32/Wbem:/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/mnt/c/Program Files/dotnet/:/mnt/c/Go/bin:/mnt/c/Program Files (x86)/nodejs/:/mnt/c/WINDOWS/System32/OpenSSH/:/mnt/c/Program Files/Git/cmd:/mnt/c/Program Files (x86)/Microsoft VS Code/bin:/mnt/c/Program Files/Azure Data Studio/bin:/mnt/c/Program Files/Microsoft VS Code Insiders/bin:/mnt/c/Program Files/PowerShell/7/:/mnt/c/Program Files/Docker/Docker/resources/bin:/mnt/c/ProgramData/DockerDesktop/version-bin:/mnt/c/Program Files/Docker/Docker/Resources/bin:… <truncated>
从这个例子中可以看出,Linux 中的PATH
变量不仅包含常见的路径,如/home/stuart/bin
,还包含已经转换为使用 WSL 挂载的 Windows PATH
变量的值,例如/mnt/c/WINDOWS/System32
。由此产生的结果是,您习惯于在 Windows 中无需指定路径即可运行的任何应用程序也可以在 WSL 中无需指定路径运行。一个区别是在 Windows 中,我们不需要指定文件扩展名(例如,我们可以在 PowerShell 中运行calc
),但在 WSL 中我们需要。
在上一节中,我们在 Windows 中创建了一个文本文件(c:\wsl-book\wsl.txt
),并使用vi
在 Linux 中打开了它,但是如果我们想在 Windows 应用程序中打开该文件怎么办?如果您尝试从 Linux 运行notepad.exe c:\wsl-book\wsl.txt
,记事本将显示找不到该文件的错误。要解决此问题,您可以将路径放在引号中(notepad.exe "c:\wsl-book\wsl.txt"
)或转义反斜杠(notepad.exe c:\\wsl-book\\wsl.txt
)。使用这两种修复方法之一,该命令将启动记事本并打开指定的文件。
实际上,当您在 WSL 发行版的终端中工作时,您将花费大量时间在 Linux 文件系统中处理文件,并且您将希望在编辑器中打开这些文件。如果您有本书的示例代码(您可以在github.com/PacktPublishing/Windows-Subsystem-for-Linux-2-WSL-2-Tips-Tricks-and-Techniques
找到它),请在终端中导航到chapter-05
文件夹,其中有一个example.txt
文件(如果您没有示例,请运行echo "Hello from WSL!" > example.txt
创建一个测试文件)。在终端中,尝试运行notepad.exe example.txt
- 这将使用 WSL 文件系统加载example.txt
文件启动记事本。这非常方便,因为它允许您轻松启动 Windows GUI 编辑器来处理 WSL 发行版中的文件。
在本节中,我们已经看到了如何轻松地从 WSL 调用 Windows GUI 应用程序并将路径作为参数传递。在下一节中,我们将看看如何从 WSL 调用 Windows 脚本,以及在需要时如何明确转换路径。
从 Linux 调用 Windows 脚本
如果你习惯在 Windows 中运行 PowerShell,那么你也习惯于能够直接调用 PowerShell cmdlet 和脚本。当你在 WSL 中运行 PowerShell 脚本时,有两个选择:在 Linux 上安装 PowerShell 或调用 Windows 上的 PowerShell 运行脚本。如果你对 Linux 上的 PowerShell 感兴趣,安装文档可以在docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-linux?view=powershell-7
找到。然而,由于本章重点是从 WSL 调用 Windows,我们将看看后者选项。
PowerShell 是一个 Windows 应用程序,并且在 Windows 路径中,所以我们可以在 Linux 中使用powershell.exe
来调用它,就像我们在上一节中看到的那样。要使用 PowerShell 运行命令,我们可以使用-C
开关(缩写为-Command
):
$ powershell.exe -C "Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System"
Component Information : {0, 0, 0, 0...}
Identifier : AT/AT COMPATIBLE
Configuration Data :
SystemBiosVersion : {OEMC - 300, 3.11.2650,
American Megatrends - 50008}
BootArchitecture : 3
PreferredProfile : 8
Capabilities : 2327733
...
正如你所看到的,在这里我们使用了-C
开关来运行 PowerShell 的Get-ItemProperty
cmdlet 来从 Windows 注册表中检索值。
除了能够调用 PowerShell cmdlet 外,你还可以从 Linux 调用 PowerShell 脚本。本书的附带代码包含一个名为wsl.ps1
的示例脚本。该脚本向用户打印问候语(使用传入的Name
参数),打印出当前工作目录,然后输出一些来自 Windows 事件日志的条目。从 Bash 提示符下,将工作文件夹设置为chapter-05
文件夹,我们可以运行该脚本:
$ powershell.exe -C ./wsl.ps1 -Name Stuart
Hello from WSL: Stuart
Current directory: Microsoft.PowerShell.Core\FileSystem
::\\wsl$\Ubuntu-20.04\home\stuart\wsl-book\chapter-05
Index Source Message
----- ------ -------
14954 edgeupdatem The description for Event ID '0'...
14953 edgeupdate The description for Event ID '0'...
14952 ESENT svchost (15664,D,50) DS_Token_DB...
14951 ESENT svchost (15664,D,0) DS_Token_DB:...
14950 ESENT svchost (15664,U,98) DS_Token_DB...
14949 ESENT svchost (15664,R,98) DS_Token_DB...
14948 ESENT svchost (15664,R,98) DS_Token_DB...
14947 ESENT svchost (15664,R,98) DS_Token_DB...
14946 ESENT svchost (15664,R,98) DS_Token_DB...
14945 ESENT svchost (15664,P,98) DS_Token_DB...
前面的输出显示了运行我们刚刚描述的脚本的结果:
-
我们可以看到
Hello from WSL: Stuart
的输出,其中包括Stuart
(我们作为Name
参数传递的值)。 -
当前目录的输出为(
Microsoft.PowerShell.Core\FileSystem::\\wsl$\Ubuntu-20.04\home\stuart\wsl-book\chapter-05
)。 -
调用
Get-EventLog
PowerShell cmdlet 时的 Windows 事件日志条目。
这个例子展示了获取 Windows 事件日志条目,但由于它在 Windows 中运行 PowerShell,你可以访问任何 Windows PowerShell cmdlet 来检索 Windows 数据或操作 Windows。
正如你在这里看到的,能够像这里展示的那样调用 PowerShell 命令和脚本提供了一种从 Windows 获取信息的简单方法。这个例子还展示了从 WSL 传递参数(Name
)到 PowerShell 脚本,接下来,我们将进一步探讨如何结合使用 PowerShell 和 Bash 命令。
在 PowerShell 和 Bash 之间传递数据
有时,调用 PowerShell 命令或脚本就足够了,但有时你会希望在 Bash 中处理该命令的输出。在 WSL 中处理 PowerShell 脚本的输出的方式很自然:
$ powershell.exe -C "Get-Content ./wsl.ps1" | wc -l
10
正如你所看到的,这个命令演示了将执行一些 PowerShell 的输出通过管道传递到wc -l
中,它计算输入中的行数(在这个例子中为10
)。
在编写脚本时,你可能还希望将值传递给 PowerShell 脚本。在简单的情况下,我们可以使用 Bash 变量,如下所示:
$ MESSAGE="Hello"; powershell.exe -noprofile -C "Write-Host $MESSAGE"
Hello
在这里,我们在 Bash 中创建了一个MESSAGE
变量,然后在传递给 PowerShell 的命令中使用它。这种方法使用了 Bash 中的变量替换-传递给 PowerShell 的实际命令是Write-Host Hello
。这种技术适用于某些场景,但有时你实际上需要将输入传递给 PowerShell。这种方法不太直观,使用了 PowerShell 中的特殊$input
变量:
$ echo "Stuart" | powershell.exe -noprofile -c 'Write-Host "Hello $input"'
Hello Stuart
在这个例子中,你可以看到从echo "Stuart"
输出的结果被传递到 PowerShell 中,PowerShell 使用$input
变量来检索输入。这个例子被故意保持简单,以帮助展示传递输入的技巧。更常见的情况是,输入可以是文件的内容或另一个 Bash 命令的输出,而 PowerShell 命令可以是执行更丰富处理的脚本。
在本节中,您已经了解了如何从 WSL 调用 Windows 应用程序,包括如何在 GUI 应用程序中打开 WSL 文件。您还了解了如何调用 PowerShell 脚本,以及如何在 PowerShell 和 Bash 之间传递数据,以创建跨两个环境的脚本,为您提供更多编写脚本的选项。在下一节中,我们将探索一些技巧和诀窍,使集成更加紧密,进一步提高您的生产力。
互操作性技巧和诀窍
在本节中,我们将介绍一些技巧,可以在 Windows 和 WSL 之间工作时提高您的生产力。我们将看到如何使用别名来避免在执行 Windows 命令时指定扩展名,使其更加自然。我们还将看到如何将文本从 Linux 复制到 Windows 剪贴板,以及如何使 Windows 文件夹在 WSL 发行版中更加自然。之后,我们将看到如何从 Linux 中的默认 Windows 应用程序打开文件。从那里开始,我们将看到当我们将 WSL 路径作为参数传递给它们时,Windows 应用程序如何能够与 WSL 路径一起工作,以及在默认行为不起作用时如何控制映射路径。最后,我们将看到如何将 Windows 中的 SSH 密钥共享到 WSL 发行版中,以便轻松进行密钥维护。
让我们开始使用别名。
创建 Windows 应用程序的别名
正如本章前面提到的,当从 WSL 调用 Windows 应用程序时,我们需要包括文件扩展名。例如,我们需要使用notepad.exe
来启动记事本,而在 Windows 中,我们只需使用notepad
。如果您习惯于不包括文件扩展名,那么包括它可能需要一点时间来适应。
作为重新训练自己的替代方法,您可以重新训练 Bash!Bash 中的别名允许您为命令创建别名或替代名称。例如,运行alias notepad=notepad.exe
将为notepad.exe
创建一个名为notepad
的别名。这意味着当您运行notepad hello.txt
时,Bash 将将其解释为notepad.exe hello.txt
。
在终端中以交互方式运行alias
命令只会为当前 shell 实例设置别名。要永久添加别名,请将alias
命令复制到您的.bashrc
(或.bash_aliases
)文件中,以便每次启动 shell 时自动设置它。
接下来,我们将看一下一个方便的 Windows 实用程序,它是一个很好的别名候选者。
将输出复制到 Windows 剪贴板
Windows 已经有了clip.exe
实用程序很长时间了。clip.exe
的帮助文本指出它将命令行工具的输出重定向到 Windows 剪贴板,这是一个很好的描述。正如我们在本章前面看到的,我们可以将 WSL 的输出导入到 Windows 应用程序中,并且我们可以使用clip.exe
将其放入 Windows 剪贴板中。
例如,运行echo $PWD > clip.exe
将当前工作目录在终端中(即$PWD
的值)传输到clip.exe
。换句话说,您可以将当前工作目录从 WSL 复制到 Windows 剪贴板中。
您还可以将其与别名(alias clip=clip.exe
)结合使用,简化为echo $PWD > clip
。
我经常使用clip.exe
- 例如,将命令的输出复制到我的代码编辑器或电子邮件中 - 这样可以避免在终端中选择和复制文本。
让我们继续使用一些技巧,看看如何使 Windows 路径在 WSL 中更加自然。
使用符号链接使 Windows 路径更易访问
正如我们之前看到的,我们可以通过/mnt/c/…
映射访问 Windows 路径。但是,您可能会发现有一些路径您经常访问,并且希望更方便地访问它们。对我来说,其中一个路径是我的 Windows Downloads
文件夹 - 每当我发现一个我想要在 WSL 中安装的 Linux 工具并需要下载一个安装包时,我的浏览器默认将其下载到 Windows 的Downloads
文件夹中。虽然我可以通过/mnt/c/Users/stuart/Downloads
访问它,但我更喜欢在 WSL 中将其访问为~/Downloads
。
为了实现这一点,我们可以使用ln
实用程序创建一个以 Windows Downloads
文件夹为目标的~/Downloads
:
$ ln -s /mnt/c/Users/stuart/Downloads/ ~/Downloads
$ ls ~/Downloads
browsh_1.6.4_linux_amd64.deb
devcontainer-cli_linux_amd64.tar.gz
powershell_7.0.0-1.ubuntu.18.04_amd64.deb
windirstat1_1_2_setup.exe
wsl_update_x64.msi
在此输出中,您可以看到使用ln -s /mnt/c/Users/stuart/Downloads/ ~/Downloads
命令创建符号链接(您需要更改第一个路径以匹配您的 Windows Downloads
文件夹)。之后,您可以看到在 WSL 中列出新的符号链接位置的内容输出。
虽然在 WSL 中没有特殊的符号链接功能,但能够创建指向 Windows 文件夹的符号链接使您能够进一步自定义 WSL 环境。当您使用 WSL 时,您可能会发现自己想要创建符号链接的文件夹。
接下来,我们将看一下如何在默认的 Windows 编辑器中打开 WSL 文件。
使用 wslview 启动默认的 Windows 应用程序
在本章中,我们已经看到了如何从 WSL 调用特定的 Windows 应用程序。Windows 还具有另一个功能,即能够启动一个文件并让 Windows 确定应该启动哪个应用程序来打开它。例如,在 PowerShell 提示符下执行example.txt
将打开默认的文本编辑器(通常是记事本),而执行example.jpg
将打开默认的图像查看器。
幸运的是,有帮助可得,wslutilities
中的wslview
允许我们从 Linux 中执行相同的操作。在 Microsoft Store 中的最新版本的 Ubuntu 预装了wslutilities
,但其他发行版的安装说明可以在github.com/wslutilities/wslu
找到。
安装了wslutilities
后,您可以在 WSL 终端中运行wslview
:
# Launch the default Windows test editor
$ wslview my-text-file.txt
# Launch the default Windows image viewer
wslview my-image.jpg
# Launch the default browser
wslview https://wsl.tips
这些命令展示了使用wslview
的几个示例。前两个示例展示了根据文件扩展名启动默认的 Windows 应用程序。第一个示例启动默认的 Windows 文本编辑器(通常是记事本),第二个示例启动与 JPEG 文件关联的 Windows 应用程序。在第三个示例中,我们传递了一个 URL,这将在默认的 Windows 浏览器中打开该 URL。
这个实用程序是从 WSL 控制台到 Windows 图形应用程序的一种非常方便的桥梁。
在撰写本文时,wslview
可以使用的路径存在一些限制;例如,wslview ~/my-text-file.txt
将失败并显示错误系统找不到指定的文件
。在下一节中,我们将介绍如何在 Windows 和 Linux 之间转换路径以解决这个问题。
在 Windows 和 WSL 之间映射路径
在本章的前面部分,我们在 WSL 中运行了诸如notepad.exe example.txt
之类的命令,结果记事本打开了我们指定的文本文件。乍一看,似乎 WSL 在我们运行命令时为我们转换了路径,但下面的屏幕截图显示了任务管理器中的记事本(添加了命令行列):
图 5.3 - 显示任务管理器中运行的 notepad.exe 的屏幕截图
在此屏幕截图中,您可以看到记事本使用了三个不同的参数:
-
notepad.exe example.txt
-
notepad.exe ../chapter-05/example.txt
-
notepad.exe /home/stuart/wsl-book/chapter-05/example.txt
对于列出的每个示例,我确保我在一个目录中,该目录解析为 WSL 中的一个文件,并且每次 Notepad 启动时,示例文件都会被打开,即使参数直接传递给 Notepad 而不进行转换(如图 5.3中的截图所示)。
这个工作方式对于我们作为 WSL 用户非常有帮助,但是虽然在这种情况下它可以正常工作,以及大多数其他情况下,了解它为什么可以正常工作对于它无法正常工作的情况也是有用的。这样,您就知道何时可能需要更改行为,例如在从 WSL 调用 Windows 脚本时。那么,如果在调用命令时路径没有被转换,记事本是如何在 WSL 中找到example.txt
的呢?答案的第一部分是,当 WSL 启动记事本时,它的工作目录被设置为与 WSL 终端的当前工作目录相对应的\\wsl$\...
路径。我们可以通过运行powershell.exe ls
来确认这种行为:
$ powershell.exe ls
Directory: \\wsl$\Ubuntu-20.04\home\stuart\wsl-book\chapter-05
Mode LastWriteTime Length Name
---- ------------- ------ ----
------ 01/07/2020 07:57 16 example.txt
$
在这个输出中,您可以看到从 WSL 启动的 PowerShell 列出了其当前工作目录的内容。WSL shell 的工作目录是/home/stuart/wsl-book/chapter-05
,当启动 PowerShell 时,它会得到 Windows 的等效路径,即\\wsl$\Ubuntu-20.04\home\stuart\wsl-book\chapter-05
。
现在我们知道记事本的工作目录是基于 WSL 工作目录的,我们可以看到在我们的前两个示例(notepad.exe example.txt
和notepad.exe ../chapter-05/example.txt
)中,记事本将路径视为相对路径,并根据其工作目录解析它们以找到文件。
最后一个示例(notepad.exe /home/stuart/wsl-book/chapter-05/example.txt
)略有不同。在这种情况下,记事本将路径解析为根相对路径。如果记事本的工作目录是C:\some\folder
,那么它将将路径解析为相对于其工作目录的根目录(C:\
),并生成路径C:\home\stuart\wsl-book\chapter-05\example.txt
。然而,由于我们是从 WSL 启动记事本的,它的工作目录是\\wsl$\Ubuntu-20.04\home\stuart\wsl-book\chapter-05
,这是一个 UNC 路径,因此根被认为是\\wsl$\Ubuntu-20.04
。这非常好,因为它映射到Ubuntu-20.04
发行版文件系统的根目录,所以将 Linux 绝对路径添加到它上面生成了预期的路径!
这种映射非常高效,大部分情况下都能正常工作,但在前面的部分中,我们看到wslview ~/my-text-file.txt
无法正常工作。当我们需要自己控制路径映射时,我们有另一个工具可以使用,接下来我们将看看它。
介绍 wslpath
wslpath
实用程序可用于在 Windows 路径和 Linux 路径之间进行转换。例如,要将 WSL 路径转换为 Windows 路径,我们可以运行以下命令:
$ wslpath -w ~/my-text-file.txt
\\wsl$\Ubuntu-20.04\home\stuart\my-text-file.txt
这个输出显示wslpath
返回了我们作为参数传递的 WSL 路径的\\wsl$\...
路径。
我们还可以使用wslpath
将路径转换为相反的方向:
$ wslpath -u '\\wsl$\Ubuntu-20.04\home\stuart\my-text-file.txt'
/home/stuart/my-text-file.txt
在这里,我们可以看到\\wsl$\...
路径已经被转换回 WSL 路径。
重要提示
在 Bash 中指定 Windows 路径时,您必须对它们进行转义或用单引号括起来,以避免需要转义。对于\\wsl$\...
路径中的美元符号也是如此。
在前面的示例中,我们使用的是 WSL 文件系统中的文件路径,但wslpath
同样适用于 Windows 文件系统中的路径:
$ wslpath -u 'C:\Windows'
/mnt/c/Windows
$ wslpath -w /mnt/c/Windows
C:\Windows
在这个输出中,您可以看到wslpath
将 Windows 文件系统中的路径转换为/mnt/...
路径,然后再转换回来。
现在我们已经看到了wslpath
的工作原理,让我们来看几个使用它的示例。
wslpath 的使用
在本章的早些时候,我们看到了方便的wslview
实用程序,但观察到它只处理相对的 WSL 路径,因此我们不能使用wslview /home/stuart/my-text-file.txt
。但是wslview
可以使用 Windows 路径,并且我们可以利用wslpath
来实现这一点。例如,wslview $(wslpath -w /home/stuart/my-text-file.txt)
将使用wslpath
将路径转换为相应的 Windows 路径,然后使用该值调用wslview
。我们可以将所有这些封装到一个函数中以便使用:
# Create a 'wslvieww' function
wslvieww() { wslview $(wslpath -w "$1"); };
# Use the function
wslvieww /home/stuart/my-text-file.txt
在此示例中,使用 Bash 创建了一个名为wslvieww
的函数(额外的w
是为了 Windows),但如果您愿意,可以选择其他名称。然后,以与wslview
相同的方式调用新函数,但这次执行路径映射,Windows 能够解析映射的路径并在文本编辑器中加载它。
我们之前看到的另一个可以使用wslpath
的示例是在 Linux 的home
文件夹中创建指向 Windows 的Downloads
文件夹的符号链接。本章前面给出的命令要求您编辑命令以将适当的路径放入 Windows 用户配置文件中。以下一组命令将在不修改的情况下执行此操作:
WIN_PROFILE=$(cmd.exe /C echo %USERPROFILE% 2>/dev/null)
WIN_PROFILE_MNT=$(wslpath -u ${WIN_PROFILE/[$'\r\n']})
ln -s $WIN_PROFILE_MNT/Downloads ~/Downloads
这些命令显示了调用 Windows 以获取USERPROFILE
环境变量,然后使用wslpath
将其转换为/mnt/…
路径。最后,将其与Downloads
文件夹组合,并传递给ln
以创建符号链接。
这些只是wslpath
用于在 Windows 和 WSL 文件系统之间转换路径时的一些示例。大多数情况下,这是不需要的,但了解它的存在(以及如何使用它)可以帮助您在 WSL 中高效地处理文件。
我们将看一下如何在 Windows 和 WSL 发行版之间共享 SSH 密钥的最后一个提示。
SSH 代理转发
在使用 SSH 连接远程机器时,通常会使用 SSH 身份验证密钥。SSH 密钥也可以用于身份验证其他服务,例如通过git
将源代码更改推送到 GitHub。
本节将指导您配置用于 WSL 发行版的 OpenSSH 身份验证代理。假设您已经拥有 SSH 密钥和一台要连接的机器。
提示
如果您没有 SSH 密钥,可以参考 OpenSSH 文档中的创建方法:docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_keymanagement
。
如果您没有要连接的机器,Azure 文档将帮助您创建具有 SSH 访问权限的虚拟机(您可以使用免费试用版进行操作):docs.microsoft.com/en-us/azure/virtual-machines/linux/ssh-from-windows#provide-an-ssh-public-key-when-deploying-a-vm
。
如果您在 Windows 和一个或多个 WSL 发行版中使用 SSH 密钥,您可以每次复制 SSH 密钥。另一种选择是在 Windows 中设置OpenSSH 身份验证代理,然后配置 WSL 发行版以使用该代理获取密钥。这意味着您只需要管理一个地方的 SSH 密钥,并且只需要在一个地方输入 SSH 密钥的密码(假设您正在使用密码)。
让我们开始使用 Windows 的 OpenSSH 身份验证代理。
确保 Windows 的 OpenSSH 身份验证代理正在运行
设置的第一步是确保 Windows 的 OpenSSH 身份验证代理正在运行。为此,请在 Windows 中打开服务应用程序,并向下滚动到OpenSSH 身份验证代理。如果它显示为正在运行,则右键单击并选择属性。在打开的对话框中,确保具有以下设置:
-
启动类型为自动。
-
服务状态为正在运行(如果没有,请点击启动按钮)。
现在,您可以使用ssh-add
将您的密钥添加到代理中 - 例如,ssh-add ~/.ssh/id_rsa
。如果您的 SSH 密钥有密码短语,您将被提示输入密码。如果出现找不到ssh-add
的错误,则使用以下说明安装 OpenSSH 客户端:docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse
。
要检查密钥是否已正确添加,请尝试从 Windows 运行ssh
以连接到远程机器:
C:\ > ssh stuart@sshtest.wsl.tips
key_load_public: invalid format
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 5.3.0-1028-azure x86_64)
Last login: Tue Jul 7 21:24:59 2020 from 143.159.224.70
stuart@slsshtest:~$
在此输出中,您可以看到ssh
正在运行并成功连接到远程机器。
提示
如果您已经配置了 SSH 密钥用于与 GitHub 进行身份验证,您可以使用ssh -T git@github.com
来测试您的连接。有关在 GitHub 上使用 SSH 密钥的完整详细信息,请访问docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh
。
告诉 Git 使用GIT_SSH
环境变量为C:\Windows\System32\OpenSSH\ssh.exe
(或者如果您的 Windows 文件夹不同,则为安装路径)。
到目前为止,我们已经在 Windows 中配置了 OpenSSH 身份验证代理,并使用了我们的 SSH 密钥。如果我们的密钥有密码短语,这将避免我们每次使用它们时都被提示输入密码。接下来,我们将设置从 WSL 访问这些密钥。
从 WSL 配置访问 Windows SSH 密钥
现在我们已经在 Windows 中使密钥工作,我们希望在 WSL 中设置我们的 Linux 发行版以连接到 Windows 的 OpenSSH 身份验证代理。Linux ssh
客户端具有SSH_AUTH_SOCK
环境变量,允许您在检索 SSH 密钥时提供一个套接字供ssh
连接。挑战在于 OpenSSH 身份验证代理允许通过 Windows 命名管道进行连接,而不是套接字(更不用说是一个单独的机器了)。
为了将 Linux 套接字连接到 Windows 命名管道,我们将使用两个实用程序:socat
和npiperelay
。socat
实用程序是一个强大的 Linux 工具,可以在不同位置之间中继流。我们将使用它来监听SSH_AUTH_SOCK
套接字并转发到一个它执行的命令。该命令将是npiperelay
实用程序(由 Windows 团队的开发人员 John Starks 编写,他在 Linux 和容器方面做了很酷的工作),它将将其输入转发到一个命名管道。
要安装npiperelay
,请从 GitHub 获取最新版本(github.com/jstarks/npiperelay/releases/latest
)并将npiperelay.exe
提取到您的路径中的位置。要安装socat
,请运行sudo apt install socat
。
要开始转发 SSH 密钥请求,请在 WSL 中运行以下命令:
export SSH_AUTH_SOCK=$HOME/.ssh/agent.sock
socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork &
第一行设置了SSH_AUTH_SOCK
环境变量。第二行运行socat
并告诉它监听SSH_AUTH_SOCK
套接字并将其中继到npiperelay
。npiperelay
命令行告诉它监听并将其输入转发到//./pipe/openssh-ssh-agent
命名管道。
有了这个设置,您现在可以在 WSL 发行版中运行ssh
:
$ ssh stuart@sshtest.wsl.tips
agent key RSA SHA256:WEsyjMl1hZY/xahE3XSBTzURnj5443sg5wfuFQ+bGLY returned incorrect signature type
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 5.3.0-1028-azure x86_64)
Last login: Wed Jul 8 05:45:15 2020 from 143.159.224.70
stuart@slsshtest:~$
此输出显示在 WSL 发行版中成功运行ssh
。我们可以通过使用-v
(详细)开关运行ssh
来验证密钥是否已从 Windows 加载:
$ ssh -v stuart@sshtest.wsl.tips
...
debug1: Offering public key: C:\\Users\\stuart\\.ssh\\id_rsa RSA SHA256:WEsyjMl1hZY/xahE3XSBTzURnj5443sg5wfuFQ+bGLY agent
debug1: Server accepts key: C:\\Users\\stuart\\.ssh\\id_rsa RSA SHA256:WEsyjMl1hZY/xahE3XSBTzURnj5443sg5wfuFQ+bGLY agent
...
完整的详细输出非常长,但在其中的这个片段中,我们可以看到ssh
用于建立连接的密钥。请注意,路径是 Windows 路径,显示密钥是通过 Windows OpenSSH 代理加载的。
我们之前运行的命令启动了socat
,使我们能够测试这种情况,但您可能希望自动转发 SSH 密钥请求,而不需要在每个新的终端会话中运行这些命令。为了实现这一点,请将以下行添加到您的.bash_profile
文件中:
export SSH_AUTH_SOCK=$HOME/.ssh/agent.sock
ALREADY_RUNNING=$(ps -auxww | grep -q "[n]piperelay.exe -ei -s //./pipe/openssh-ssh-agent"; echo $?)
if [[ $ALREADY_RUNNING != "0" ]]; then
if [[ -S $SSH_AUTH_SOCK ]]; then
(http://www.tldp.org/LDP/abs/html/fto.html)
echo "removing previous socket..."
rm $SSH_AUTH_SOCK
fi
echo "Starting SSH-Agent relay..."
(setsid socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork &) /dev/null 2>&1
fi
这些命令的本质与原始的socat
命令相同,但增加了错误检查,在启动之前测试socat
命令是否已经运行,并允许它在终端会话之间持久存在。
有了这个设置,您可以有一个地方来管理您的 SSH 密钥和密码短语(Windows 的 OpenSSH 身份验证代理),并无缝共享您的 SSH 密钥与您的 WSL 发行版。
此外,将 Linux 套接字转发到 Windows 命名管道的技术可以在其他情况下使用。请查看npiperelay
文档以获取更多示例,包括从 Linux 连接到 Windows 中的 MySQL 服务:github.com/jstarks/npiperelay
。
在这个技巧和窍门的部分,您已经看到了一系列示例,说明了在 WSL 和 Windows 之间桥接的技术,从创建命令别名到共享 SSH 密钥。虽然这些示例的目的是直接使用,但它们背后的技术是可推广的。例如,SSH 密钥共享示例展示了如何使用一些工具来实现 Linux 套接字和 Windows 命名管道之间的桥接,并可以在其他场景中使用。
总结
在本章中,您已经学会了如何从 WSL 发行版访问 Windows 文件系统中的文件,以及如何从 Linux 启动 Windows 应用程序,包括使用wlsview
实用程序轻松启动文件的默认 Windows 应用程序。您已经学会了如何在 Windows 和 Linux 脚本之间传递输入,包括在需要时如何使用wslpath
映射两个文件系统方案之间的路径。
在本章的结尾,您了解了如何将 Linux 套接字映射到 Windows 命名管道,并使用此技术使您的 Windows SSH 密钥在 WSL 中可用。这使您可以避免将 SSH 密钥复制到每个 WSL 发行版中,而是在一个共享的地方管理您的 SSH 密钥和密码短语,从而更容易控制和备份您的 SSH 密钥。
所有这些都有助于通过 WSL 将 Windows 和 Linux 更紧密地联系在一起,并在您的日常工作流程中提高生产力。
在本章中,我们在终端上花了相当多的时间。在下一章中,我们将重新访问 Windows 终端,并探索一些更高级的方法来自定义它以满足您的需求。
第六章:从 Windows 终端获取更多信息
新的 Windows 终端在第三章,开始使用 Windows 终端中介绍过,您已经了解了如何安装它以及如何自定义配置文件的顺序和它们在该章节中使用的颜色方案。在本章中,我们将进一步探索 Windows 终端,并介绍一些在 Windows 终端中运行多个不同 shell 的方法。之后,我们将介绍如何添加自定义配置文件,以简化常见任务的流程。
在本章中,我们将涵盖以下主要内容:
-
自定义选项卡标题
-
使用多个窗格
-
添加自定义配置文件
我们将从查看如何使用选项卡标题来帮助您管理多个选项卡开始本章。
自定义选项卡标题
选项卡式用户界面很棒;浏览器有它们,编辑器有它们,Windows 终端也有它们。对于某些人,包括我自己在内,选项卡式用户界面也带来了一些挑战 - 我打开了很多选项卡:
图 6.1 - Windows 终端的屏幕截图,打开了许多选项卡
正如前面的屏幕截图所示,打开多个选项卡时,很难确定每个选项卡正在运行的内容以及您使用它的目的。当我编码时,我经常打开一个选项卡用于执行 Git 操作,另一个用于构建和运行代码,另一个用于在代码运行时与代码进行交互。除此之外,还有一个额外的选项卡用于一些常规系统交互,以及一个或两个选项卡用于查看其他项目中的问题。这样,选项卡的数量很快就增加了。
前面的屏幕截图显示,根据选项卡中运行的 shell,您可能会获得一些路径信息,但是如果在相同路径下有多个选项卡,即使这样也没有太大帮助,因为它们都显示相同的值。幸运的是,使用 Windows 终端,您可以设置选项卡标题以帮助您跟踪。我们将介绍几种不同的方法,以便您可以选择最适合您的方法。
从上下文菜单设置选项卡标题
设置标题的简单方法是右键单击选项卡标题,弹出上下文菜单,然后选择重命名选项卡:
图 6.2 - 显示重命名选项卡的选项卡上下文菜单的屏幕截图
正如前面的屏幕截图所示,右键单击选项卡会弹出上下文菜单,允许您重命名选项卡或设置选项卡颜色以帮助组织您的选项卡:
图 6.3 - Windows 终端的屏幕截图,显示已重命名和带颜色的选项卡
此屏幕截图显示了按照选项卡标题中的颜色进行分组的选项卡标题集合。每个选项卡还有一个描述性标题,例如git,表示该选项卡的用途。当然,您可以选择适合您工作流程的标题。
当您在终端中工作时,您可能更喜欢使用键盘来设置标题,因此我们将在下一节中介绍这一点。
使用函数从 shell 设置选项卡标题
如果您喜欢保持双手在键盘上,可以从选项卡中运行的 shell 中设置选项卡标题。这取决于您使用的 shell 的方法,因此我们将在这里介绍一些不同的 shell。让我们从Bash开始。
为了方便设置提示符,我们可以创建以下函数:
function set-prompt() { echo -ne '\033]0;' $@ '\a'; }
从此代码片段中可以看出,这创建了一个名为set-prompt
的函数。该函数使用控制终端标题的转义序列,允许我们运行诸如set-prompt "A new title"
的命令来更改选项卡标题,在此示例中将其更改为A new title
。
对于 PowerShell,我们可以创建一个类似的函数:
function Set-Prompt {
param (
# Specifies a path to one or more locations.
[Parameter(Mandatory=$true,
ValueFromPipeline=$true)]
[ValidateNotNull()]
[string]
$PromptText
)
$Host.UI.RawUI.WindowTitle = $PromptText
}
这段代码显示了一个Set-Prompt
函数,它访问 PowerShell 的$Host
对象来控制标题,允许我们运行诸如Set-Prompt "A new title"
之类的命令以类似于 Bash 的方式更改选项卡标题。
对于 Windows 命令提示符(cmd.exe
),我们可以运行TITLE A new title
来控制选项卡标题。
提示
一些实用程序和 shell 配置会覆盖默认的提示设置,以控制 shell 标题以及提示。在这些情况下,本节中的函数将不会有任何明显的效果,因为提示将立即覆盖指定的标题。如果您在使用这些函数时遇到问题,请检查您的提示配置。
对于 Bash,运行 echo $PROMPT_COMMAND
来检查您的提示配置。对于 PowerShell,运行Get-Content function:prompt
。
这里显示了使用刚才看到的函数的示例:
图 6.4 - 显示使用 set-prompt 函数的屏幕截图
在此屏幕截图中,您可以看到在 Bash 中使用set-prompt
函数来控制选项卡标题。其他选项卡(PowerShell 和命令提示符)的标题也是使用本节中显示的函数设置的。
在终端中工作时,使用这些函数可以方便地更新选项卡标题,而无需中断您的工作流程以使用鼠标。您还可以使用这些函数作为脚本的一部分来更新标题,例如,通过选项卡标题以一目了然的方式查看长时间运行脚本的状态,即使不同的选项卡具有焦点。
我们将要看的最后一种更新选项卡标题的方法是在启动 Windows 终端时通过命令行进行。
从命令行设置选项卡标题
前一节介绍了如何从运行的 shell 中设置选项卡标题;在本节中,我们将启动 Windows 终端并传递命令行参数来指定要加载的配置文件和设置选项卡标题。
可以使用wt.exe
命令从命令行或运行对话框(Windows + R)启动 Windows 终端。仅运行wt.exe
将使用默认配置文件启动 Windows 终端。可以使用--title
开关来控制选项卡标题,例如,wt.exe --title "Put a title here"
。此外,--profile
(或-p
)开关允许我们指定要加载的配置文件,因此wt.exe -p Ubuntu-20.04 --title "This is Ubuntu"
将加载Ubuntu-20.04
配置文件并设置选项卡标题。
控制选项卡标题的一个动机是在使用多个选项卡时进行跟踪。Windows 终端具有一组强大的命令行参数(我们将在下一节中看到更多),允许我们使用一个或多个特定的选项卡/配置文件启动终端。我们可以在前面的命令后面添加;new-tab
(注意分号),以指定要加载的新选项卡,包括任何其他参数,如title
和profile
:
wt.exe -p "PowerShell" --title "This one is PowerShell"; new-tab -p "Ubuntu-20.04" --title "WSL here!"
在此示例中,我们将第一个选项卡指定为PowerShell
配置文件,并将其标题设置为This one is PowerShell
,第二个选项卡指定为Ubuntu-20.04
配置文件,并将其标题设置为WSL here!
。
注意
new-tab
参数需要一个分号在其前面,但许多 shell(包括 Bash 和 PowerShell)将分号视为命令分隔符。为了成功使用前面的命令,任何分号都需要在 PowerShell 中使用反引号进行转义(``;`)。
正如在第五章中所见,Linux 与 Windows 的互操作性,在从 Linux 调用 Windows 应用程序部分,我们可以从 WSL 启动 Windows 应用程序。通常情况下,我们可以直接执行 Windows 应用程序,但由于 Windows 终端使用了一个称为执行别名的功能,我们需要通过cmd.exe
来启动它。
此外,由于wt.exe
的工作方式,当从 Bash 启动时,需要使用cmd.exe
运行:
cmd.exe /C wt.exe -p "PowerShell" --title "这是 PowerShell"\; new-tab -p "Ubuntu-20.04" --title "在这里运行 WSL!"
这个示例展示了如何使用cmd.exe
启动 Windows 终端并打开多个选项卡(注意反斜杠用于转义分号),设置配置文件和标题。
使用 Windows 终端的new-tab
命令可以重复多次,通过这种方式,你可以创建命令或脚本来以可重复的方式设置复杂的 Windows 终端选项卡布局。
本节介绍的技巧提供了多种方法来设置 Windows 终端会话中选项卡的标题,帮助你在不同选项卡中打开多个 Shell 时保持组织。在下一节中,我们将介绍 Windows 终端的另一个用于处理多个 Shell 的功能。
使用多个窗格
在前一节中,我们看到了在同时打开多个 Shell 时使用选项卡的情况,但有时候希望能够同时看到多个 Shell。在本节中,我们将介绍如何在 Windows 终端中使用多个窗格来实现这样的效果:
图 6.5 - 展示 Windows 终端中多个窗格的屏幕截图
上面的屏幕截图显示了在同一个选项卡中运行多个配置文件的窗格:左侧是一个已经发出了网络请求的 PowerShell 窗口,右上角的窗格正在运行一个 Web 服务器,右下角的窗格正在运行htop
以跟踪 WSL 中正在运行的 Linux 进程。
提示
如果你熟悉tmux
实用程序(github.com/tmux/tmux/wiki
),那么这可能看起来很熟悉,因为tmux
也允许将窗口分割成多个面板。但是有一些区别。tmux
的一个特性是允许你断开和重新连接终端会话,这在使用ssh
时非常方便,因为它可以保留你的会话,而tmux
则不会。
在上面的屏幕截图中,你可以看到 PowerShell 和 Bash(在 WSL 中)在同一个选项卡的不同窗格中运行。了解tmux
和 Windows 终端的功能,并选择适合工作的正确工具是很重要的 - 你始终可以在 Windows 终端的 Bash shell 中运行 tmux,以获得最佳的体验!
现在你对窗格有了一定的了解,让我们看看如何设置它们。
交互式创建窗格
创建窗格的最简单方法是按需交互式创建。有一些默认的快捷键可以帮助你入门,但如果你有特定的需求,你可以根据这里的描述配置自己的按键绑定:docs.microsoft.com/en-us/windows/terminal/customize-settings/key-bindings#pane-management-commands
。
首先是Alt + Shift + -, 这将把当前窗格水平分割成两半,然后是Alt + Shift + +, 这将把窗格垂直分割。这两个命令都会在新创建的窗格中启动默认配置文件的新实例。
默认配置文件可能不是你想要运行的配置文件,但通常情况下,你可能希望在同一个配置文件中运行另一个终端。按下Alt + Shift + D将在当前窗格中创建一个新的配置文件实例的窗格。该命令会根据可用空间自动确定是水平分割还是垂直分割。
如果你想选择在新窗格中打开哪个配置文件,你可以打开启动配置文件下拉菜单:
图 6.6 - 展示启动配置文件下拉菜单的屏幕截图
此屏幕截图显示了用于选择要运行的配置文件的标准下拉菜单。与正常点击不同,按住Alt键并单击将在新窗格中启动所选配置文件。与Alt + Shift + D一样,Windows 终端将确定是水平拆分还是垂直拆分当前窗格。
另一个选项是使用 Windows 终端命令面板,使用Ctrl + Shift + P:
图 6.7-屏幕截图显示命令面板中的拆分选项
命令面板允许您输入以过滤命令列表,并且此屏幕截图显示与split
匹配的命令。底部的两个命令与我们已经看到的两个命令以及它们对应的快捷键匹配。顶部的命令在命令面板中提供了一个菜单系统,允许您选择要用于新窗格的配置文件,然后选择如何拆分现有窗格。
现在我们已经看过如何创建窗格,让我们看一下如何使用它们。
管理窗格
在窗格之间切换焦点最明显的方法是使用鼠标在窗格中单击-这样做会更改焦点所在的窗格(窗格边框上会显示突出显示的颜色)。要使用键盘更改窗格,可以使用Alt + 光标键,即Alt + 光标向上将焦点移动到当前窗格上方的窗格。
要更改窗格的大小,我们使用类似的键组合,即Alt + Shift + 光标键。Alt + Shift + 光标向上和Alt + Shift + 光标向下组合调整当前窗格的高度,Alt + Shift + 光标向左和Alt + Shift + 光标向右组合调整当前窗格的宽度。
如果任何在窗格中运行的 shell 退出,则该窗格将关闭,并且其他窗格将调整大小以填充其空间。您还可以通过按下Ctrl + Shift + W关闭当前窗格(此快捷键在第三章中引入,使用 Windows 终端部分,作为关闭选项卡的快捷键,但在那时,选项卡中只有一个窗格!)。
最后,让我们看一下如何在从命令行启动 Windows 终端时配置窗格。
从命令行创建窗格
在本章的前面部分,我们看到了如何使用 Windows 终端命令行(wt.exe
)加载多个选项卡启动 Windows 终端。在本节中,我们将看到如何使用窗格执行相同操作。当您在项目上工作并且有一组常用的窗格设置时,这非常有用,因为您可以对其进行脚本处理,并且可以轻松启动一致的布局。
在使用多个选项卡启动时,我们使用wt.exe
的new-tab
命令。启动多个窗格的方法类似,但使用split-pane
命令(请注意,分号的转义规则仍适用于“从命令行设置选项卡标题”部分)。
以下是使用split-pane
的示例:
wt.exe -p PowerShell; split-pane -p Ubuntu-20.04 -V --title "web server"; split-pane -H -p Ubuntu-20.04 --title htop bash -c htop
如您所见,在此示例中,split-pane
用于指定新窗格,我们可以使用-p
开关指定该窗格应使用的配置文件。我们可以让 Windows 终端自动选择如何拆分,或者我们可以使用-H
进行水平拆分,或者使用-V
进行垂直拆分。您可能还注意到已指定了--title
。Windows 终端允许每个窗格都有一个标题,并将当前焦点窗格的标题显示为选项卡标题。最后,您可能会注意到最后一个窗格具有附加参数bash -c htop
。这些参数被视为在启动的配置文件中执行的命令。此命令的最终结果与图 6.5中显示的屏幕截图非常相似。
作为一个额外的功能,Windows Terminal 中的命令面板还允许我们使用命令行选项。按下Ctrl + Shift + P来打开命令面板,然后输入>
(右尖括号):
图 6.8 - 屏幕截图显示带有命令行选项的命令面板
正如您在这个屏幕截图中所看到的,我们可以使用split-pane
命令使用命令行选项来拆分现有的窗格。
到目前为止,在本章中,我们已经介绍了一些使用选项卡和窗格来帮助管理多个配置文件的方法。在本章的最后一节中,我们将看一些其他的配置文件创意想法。
添加自定义配置文件
Windows Terminal 非常好地自动发现 PowerShell 安装和 WSL 分发,以填充您的配置文件列表(并在安装新分发时更新)。这是一个很好的开始,但除了启动交互式 shell 之外,配置文件还可以在配置文件中启动特定的应用程序(就像上一节中显示的htop
)。在本节中,我们将看一些示例,但它们的主要目的是展示除了仅仅启动 shell 之外的想法,以激发您如何自定义 Windows Terminal 配置的灵感。
如果您经常通过 SSH 连接到一台机器,那么您可以通过创建一个直接启动 SSH 的 Windows Terminal 配置文件来简化工作流程。从配置文件下拉菜单中打开您的设置(或按Ctrl + ,),并在profiles
下的list
部分中添加一个配置文件:
{
"guid": "{9b0583cb-f2ef-4c16-bcb5-9111cdd626f3}",
"hidden": false,
"name": "slsshtest",
"commandline": "wsl bash -c \"ssh stuart@slsshtest.uksouth.cloudapp.azure.com\"",
"colorScheme": "Ubuntu-sl",
"background": "#801720",
"fontFace": "Cascadia Mono PL"
},
Windows Terminal 设置文件在第三章中介绍过,开始使用 Windows Terminal,在这个示例配置文件中,您可以看到来自该章节的熟悉属性,如name
和colorScheme
。commandline
属性是我们配置要运行的内容的地方,我们使用它来启动wsl
命令以运行带有运行ssh
命令行的bash
。您应该确保guid
值与设置中的其他配置文件不同。这个示例展示了如何创建一个在 WSL 中执行命令的配置文件 - 对于 SSH,您还可以选择在commandline
属性中直接使用ssh
,因为 Windows 现在包含了一个 SSH 客户端。
启动这个新配置文件会自动启动ssh
并连接到指定的远程机器。作为一个额外的功能,background
属性可以用来设置背景颜色,以指示您所连接的环境,例如,方便地区分开发和测试环境。
如果您有多台通过 SSH 连接的机器,那么您可以启动一个脚本来允许您选择要连接的机器:
#!/bin/bash
# This is an example script showing how to set up a prompt for connecting to a remote machine over SSH
PS3="Select the SSH remote to connect to: "
# TODO Put your SSH remotes here (with username if required)
vals=(
stuart@sshtest.wsl.tips
stuart@slsshtest.uksouth.cloudapp.azure.com
)
IFS="\n"
select option in "${vals[@]}"
do
if [[ $option == "" ]]; then
echo "unrecognised option"
exit 1
fi
echo "Connecting to $option..."
ssh $option
break
done
该脚本包含一个选项列表(vals
),当执行脚本时,这些选项将呈现给用户。当用户选择一个选项时,脚本会运行ssh
来连接到该机器。
如果您将此脚本保存为ssh-launcher.sh
并放在您的主文件夹中,您可以在 Windows Terminal 设置中添加一个配置文件来执行它:
{
"guid": "{0b669d9f-7001-4387-9a91-b8b3abb4s7de8}",
"hidden": false,
"name": "ssh picker",
"commandline": "wsl bash $HOME/ssh-launcher.sh,
"colorScheme": "Ubuntu-sl",
"fontFace": "Cascadia Mono PL"
},
在上述配置文件中,您可以看到commandline
已被替换为运行先前的ssh-launcher.sh
脚本的命令。当启动此配置文件时,它使用wsl
通过bash
来启动脚本:
图 6.9 - 屏幕截图显示 ssh 启动脚本运行
您可以在上述屏幕截图中看到此脚本的运行情况。该脚本提示用户从机器列表中选择,并运行ssh
以连接到所选的机器。这为设置到经常使用的机器的连接提供了一种方便的方式。
当您使用 WSL 时,您可能会发现一组您经常运行的应用程序或者您经常执行的步骤,这些都是添加到 Windows 终端配置文件的好选择!
注意
这里还有其他一些选项,我们没有机会在这里看到,例如为配置文件设置背景图像,您可以在 Windows 终端文档中找到这些选项的详细信息,网址为docs.microsoft.com/en-us/windows/terminal/
。Windows 终端还在快速添加新功能-要了解即将推出的功能,请查看 GitHub 上的路线图文档,网址为github.com/microsoft/terminal/raw/master/doc/terminal-v2-roadmap.md
。
总结
在本章中,您已经了解了如何使用多个 Windows 终端配置文件的方法。首先,您了解了如何通过控制选项卡标题(和颜色)来处理多个选项卡,以帮助跟踪每个选项卡的上下文。然后,您了解了如何使用窗格来允许在同一个选项卡中运行多个(可能不同的)配置文件。您可能会发现您更喜欢一种工作方式,或者将选项卡和配置文件结合起来。无论哪种方式,您还学会了如何使用 Windows 终端命令行来脚本化选项卡和窗格的创建,以便为您的项目轻松快速地创建一致且高效的工作环境。
本章最后介绍了如何使用 Windows 终端配置文件来运行不仅仅是 shell 的功能,通过设置一个启动 SSH 连接到远程机器的配置文件。然后,您了解了如何进一步选择要连接的机器列表,并使用Bash脚本提示您选择。如果您经常通过 SSH 连接到机器,那么这些示例将希望对您有用,但目标是展示如何进一步利用 Windows 终端中的配置文件。当您在工作流程中找到常见任务和应用程序时,请考虑是否值得花费几分钟创建一个 Windows 终端配置文件,以使这些重复的任务更快、更容易完成。所有这些技术都可以让您优化 Windows 终端的工作流程,提高您的日常工作效率。
在下一章中,我们将介绍一个新的主题:如何在 WSL 中使用容器。
第七章:在 WSL 中使用容器
容器作为一种打包和管理应用程序的方式是一个热门话题。虽然有 Windows 和 Linux 版本的容器,但由于本书是关于 WSL 的,我们将重点介绍 Linux 容器和 Docker 容器。如果您想了解 Windows 容器,可以从这个链接开始:docs.microsoft.com/virtualization/windowscontainers/
在介绍了容器的概念并安装了 Docker 之后,本章将指导您运行一个预构建的 Docker 容器,然后通过使用 Python Web 应用程序作为示例,教您如何构建自己应用程序的容器镜像。创建容器镜像后,您将快速了解 Kubernetes 的一些关键组件,然后看看如何使用这些组件在 WSL 中托管容器化应用程序。
在本章中,我们将涵盖以下主要内容:
-
容器概述
-
在 WSL 中安装和使用 Docker
-
使用 Docker 运行容器
-
构建和运行 Docker 中的 Web 应用程序
-
介绍编排器
-
在 WSL 中设置 Kubernetes
-
在 Kubernetes 中运行 Web 应用程序
我们将从探索容器的概念开始本章。
容器概述
容器提供了一种打包应用程序及其依赖项的方式。这个描述可能有点像一个虚拟机(VM),在虚拟机中,你可以在文件系统中安装应用程序二进制文件,然后稍后运行。然而,当你运行一个容器时,它更像一个进程,无论是启动速度还是内存消耗量。在底层,容器是一组通过使用诸如 Linux 命名空间和控制组(cgroups)等特性进行隔离的进程,使得这些进程看起来像在它们自己的环境中运行(包括有自己的文件系统)。容器与主机操作系统共享内核,因此与虚拟机相比,它们的隔离性较低,但对于许多目的来说,这种隔离已经足够了,而且主机资源的共享使得容器可以实现低内存消耗和快速启动时间。
除了容器执行外,Docker 还可以轻松定义容器的组成部分(称为容器镜像)并在注册表中发布容器镜像,供其他用户使用。
我们将在本章稍后的部分中看到这一点,但首先让我们安装 Docker。
在 WSL 中安装和使用 Docker
在 Windows 机器上运行 Docker 的传统方法是使用 Docker Desktop(https://www.docker.com/products/docker-desktop),它将为您创建和管理一个 Linux 虚拟机,并在该虚拟机中作为守护程序运行 Docker 服务。这样做的缺点是虚拟机需要时间启动,并且必须预先分配足够的内存来容纳运行各种容器。
通过 WSL2,可以在 WSL 发行版中安装和运行标准的 Linux Docker 守护程序。这样做的好处是在启动时更快,占用的内存更少,并且只在运行容器时增加内存消耗。缺点是你必须自己安装和管理守护程序。
幸运的是,现在有第三种选择,即安装 Docker Desktop 并启用 WSL 后端。通过这种方法,您可以保留 Docker Desktop 在安装和管理方面的便利性。不同之处在于,Docker Desktop 会在 WSL 中为您运行守护程序,从而使您在不失便利性的情况下获得启动时间和内存使用方面的改进。
要开始使用,请从 https://www.docker.com/products/docker-desktop 下载并安装 Docker Desktop。安装完成后,在系统图标托盘中右键单击 Docker 图标,选择“设置”。您将看到以下屏幕:
图 7.1 - Docker 设置的屏幕截图显示 WSL 2 选项
上面的截图显示了“使用基于 WSL 2 的引擎”选项。确保选中此选项以配置 Docker Desktop 在 WSL 2 下运行,而不是传统的虚拟机。
您可以从“资源”部分选择 Docker Desktop 与哪些发行版集成:
图 7.2 - WSL 集成的 Docker 设置的屏幕截图
正如您在上面的截图中看到的,您可以控制 Docker Desktop 与哪些发行版集成。当您选择与 WSL 发行版集成时,Docker 守护程序的套接字将对该发行版可用,并为您添加 docker 命令行界面(CLI)。选择您想要能够从中使用 Docker 的所有发行版,并单击“应用并重新启动”。
Docker 重新启动后,您将能够使用docker
命令行界面(CLI)与任何选定的 WSL 发行版交互,例如docker info
:
$ docker info
Client:
Debug Mode: false
Server:
...
Server Version: 19.03.12
...
Kernel Version: 4.19.104-microsoft-standard
Operating System: Docker Desktop
OSType: linux
...
这个片段显示了运行docker info
的一些输出,您可以看到服务器正在linux
上运行,内核版本为4.19.104-microsoft-standard
,这与我的机器上的 WSL 内核版本相同(您可以通过在 WSL 发行版中运行uname -r
来检查您的机器上的版本)。
有关使用 WSL 安装和配置 Docker Desktop 的更多信息,请参阅 Docker 文档 https://docs.docker.com/docker-for-windows/wsl/。
现在我们已经安装了 Docker,让我们通过运行一个容器来开始。
使用 Docker 运行容器
正如前面提到的,Docker 为我们提供了一种标准化的方式来打包容器镜像。这些容器镜像可以通过 Docker 注册表共享,Docker Hub(https://hub.docker.com/)是一个常用的公共镜像注册表。在本节中,我们将使用以下命令运行一个带有nginx
Web 服务器的容器:docker run -d --name docker-nginx -p 8080:80 nginx
。
$ docker run -d --name docker-nginx -p 8080:80 nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
8559a31e96f4: Already exists
1cf27aa8120b: Downloading [======================> ] 11.62MB/26.34MB
...
我们刚刚运行的命令的最后一部分告诉 Docker 我们要运行哪个容器镜像(nginx
)。这个输出片段显示 Docker 在本地没有找到nginx
镜像,所以它开始拉取(即下载)来自 Docker Hub 的镜像。容器镜像由多个层组成(我们将在本章后面讨论这个问题),在输出中,已经存在一个层并且正在下载另一个层。docker
命令行界面(CLI)会随着下载的进行不断更新输出,如下所示:
$ docker run -d --name docker-nginx -p 8080:80 nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
8559a31e96f4: Already exists
1cf27aa8120b: Pull complete
67d252a8c1e1: Pull complete
9c2b660fcff6: Pull complete
4584011f2cd1: Pull complete
Digest: sha256:a93c8a0b0974c967aebe868a186 e5c205f4d3bcb5423a56559f2f9599074bbcd
Status: Downloaded newer image for nginx:latest
336ab5bed2d5f547b8ab56ff39d1db08d26481215d9836a1b275e0c7dfc490d5
当 Docker 完成拉取镜像时,您将看到类似于上面输出的内容,确认 Docker 已经拉取了镜像并打印了创建的容器的 ID(336ab5bed2d5…
)。此时,我们可以运行docker ps
来列出正在运行的容器:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
336ab5bed2d5 nginx "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:8080->80/tcp| docker-nginx
这个输出显示了一个正在运行的容器,我们可以看到容器 ID 336ab5bed2d5
的值与docker run
命令输出的容器 ID 的开头匹配。默认情况下,docker ps
输出容器 ID 的短格式,而docker run
输出完整的容器 ID 值。
让我们回到我们用来运行容器的命令:docker run -d --name docker-nginx -p 8080:80 nginx
。这个命令有几个部分:
-
-d
告诉 Docker 在后台运行这个容器,即以分离模式运行。 -
--name
告诉 Docker 使用一个特定的名称docker-nginx
来命名容器,而不是生成一个随机的名称。这个名称也可以在docker ps
的输出中看到,并且可以使用。 -
-p
允许我们将主机上的端口映射到正在运行的容器内部的端口。格式为<主机端口>:<容器端口>
,因此在8080:80
的情况下,我们将主机上的端口8080
映射到容器内部的端口80
。 -
最后一个参数是要运行的镜像的名称:
nginx
。
由于端口80
是nginx
默认提供内容的端口,并且我们已将端口8080
映射到该容器端口,因此我们可以在 Web 浏览器中打开http://localhost:8080
,如下图所示:
图 7.3-浏览器显示 nginx 输出的屏幕截图
前面的屏幕截图显示了 Web 浏览器中 nginx 的输出。此时,我们使用了一个命令(docker run
)在 Docker 容器中下载和运行 nginx。容器资源具有一定的隔离级别,这意味着容器内部提供流量的端口80
在外部不可见,因此我们将其映射到容器外部的端口8080
。由于我们正在使用 WSL 2 后端的 Docker Desktop,因此端口8080
实际上在 WSL 2 虚拟机上公开,但由于我们在第四章中看到的魔法,即Windows 与 Linux 的互操作性,在从 Windows 访问 Linux Web 应用程序部分,我们可以从 Windows 访问http://localhost:8080
。
如果我们让容器继续运行,它将继续消耗资源,因此在继续之前让我们停止并删除它,如下所示:
$ docker stop docker-nginx
docker-nginx
$ docker rm docker-nginx
docker-nginx
在此输出中,您可以看到docker stop docker-nginx
,它停止了正在运行的容器。此时,它不再消耗内存或 CPU,但它仍然存在并引用了用于创建它的镜像,这会阻止删除该镜像。因此,在停止容器后,我们使用docker rm docker-nginx
来删除它。为了释放磁盘空间,我们还可以通过运行docker image rm nginx:latest
来清理nginx
镜像。
现在我们已经看到了如何运行容器,让我们构建自己的容器镜像来运行。
在 Docker 中构建和运行 Web 应用程序
在本节中,我们将构建一个 Docker 容器镜像,该镜像打包了一个 Python Web 应用程序。该容器镜像将包含 Web 应用程序及其所有依赖项,以便在安装了 Docker 守护程序的机器上运行。
要按照此示例进行操作,请确保您已经在 Linux 发行版中克隆了本书的代码(来自github.com/PacktPublishing/Windows-Subsystem-for-Linux-2-WSL-2-Tips-Tricks-and-Techniques
),然后打开终端并导航到chapter-07/01-docker-web-app
文件夹,其中包含我们将在此处使用的示例应用程序。请查看README.md
文件以获取运行应用程序所需的依赖项的安装说明。
示例应用程序基于 Python 的Flask Web 框架构建(https://github.com/pallets/flask),并使用Gunicorn HTTP 服务器托管应用程序(https://gunicorn.org/)。
为了使本章重点放在 Docker 容器上,该应用程序只有一个代码文件app.py
:
from os import uname
from flask import Flask
app = Flask(__name__)
def gethostname():
return uname()[1]
@app.route("/")
def home():
return f"<html><body><h1>Hello from {gethostname()}</h1></body></html>"
如代码所示,定义了一个用于主页的单个端点,该端点返回一个显示 Web 服务器所在机器的主机名的消息。
可以使用gunicorn --bind 0.0.0.0:5000 app:app
运行该应用程序,并在 Web 浏览器中打开http://localhost:5000
:
图 7.4-浏览器中显示示例应用程序的屏幕截图
在此屏幕截图中,您可以看到示例应用程序的响应,显示应用程序正在运行的主机名(wfhome
)。
现在您已经看到了示例应用程序的运行情况,我们将开始看如何将其打包为容器镜像。
介绍 Dockerfile
要构建一个镜像,我们需要能够向 Docker 描述镜像应该包含什么内容,为此,我们将使用一个 Dockerfile
。Dockerfile
包含了一系列命令,供 Docker 执行以构建容器镜像:
FROM python:3.8-slim-buster
EXPOSE 5000
ADD requirements.txt .
RUN python -m pip install -r requirements.txt
WORKDIR /app
ADD . /app
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
这个 Dockerfile 包含了一系列命令。让我们来看看它们:
-
FROM
命令指定 Docker 应该使用的基础镜像,换句话说,是我们容器镜像的起始内容。在基础镜像中安装的任何应用程序和软件包都成为我们在其之上构建的镜像的一部分。在这里,我们指定了python:3.8-slim-buster
镜像,它提供了一个基于python:3.8-buster
镜像的镜像,该镜像包含了一些常见的软件包,但这使得基础镜像变得更大。由于此应用程序只使用了几个软件包,我们使用了slim
变体。 -
EXPOSE
表示我们要暴露一个端口(在本例中为5000
,因为这是 Web 应用程序将监听的端口)。 -
我们使用
ADD
命令将内容添加到容器镜像中。ADD
的第一个参数指定要从host
文件夹添加的内容,第二个参数指定要将其放置在容器镜像中的位置。在这里,我们正在添加requirements.txt
。 -
RUN
命令用于使用刚刚通过ADD
命令添加到镜像中的requirements.txt
文件执行pip install
操作。 -
WORKDIR
用于将容器中的工作目录设置为/app
。 -
ADD
再次用于将完整的应用程序内容复制到/app
目录中。我们将在下一节中讨论为什么使用两个单独的ADD
命令将应用程序文件复制进去。 -
最后,
CMD
命令指定当从此镜像运行容器时将执行的命令。在这里,我们指定与刚刚在本地运行 Web 应用程序时使用的相同的gunicorn
命令。
现在我们有了一个 Dockerfile
,让我们来看看如何使用它来构建一个镜像。
构建镜像
要构建一个容器镜像,我们将使用 docker build
命令:
docker build -t simple-python-app .
在这里,我们使用了 -t
开关来指定生成的镜像应该被标记为 simple-python-app
。这是我们以后可以使用的镜像名称来运行容器。最后,我们告诉 Docker 使用哪个目录作为构建上下文,这里我们使用 .
表示当前目录。构建上下文指定要打包并传递给 Docker 守护进程用于构建镜像的内容 - 当您将文件 ADD
到 Dockerfile
时,它会从构建上下文中复制。
这个命令的输出非常长,所以我们不会完整地包含它,我们只会看一些关键部分。
这个命令的初始输出来自 FROM
命令:
Step 1/7 : FROM python:3.8-slim-buster
3.8-slim-buster: Pulling from library/python
8559a31e96f4: Already exists
62e60f3ef11e: Pull complete
...
Status: Downloaded newer image for python:3.8-slim-buster
在这里,您可以看到 Docker 已经确定它在本地没有基础镜像,所以从 Docker Hub 上拉取了它,就像我们之前运行 nginx
镜像一样。
在输出的稍后部分,我们可以看到 pip install
已经执行了安装应用程序要求的操作:
Step 4/7 : RUN python -m pip install -r requirements.txt
---> Running in 1515482d6808
Requirement already satisfied: wheel in /usr/local/lib/python3.8/site-packages (from -r requirements.txt (line 1)) (0.34.2)
Collecting flask
Downloading Flask-1.1.2-py2.py3-none-any.whl (94 kB)
Collecting gunicorn
Downloading gunicorn-20.0.4-py2.py3-none-any.whl (77 kB)
...
在上面的代码片段中,您可以看到 pip install
的输出,它正在安装 flask
和 gunicorn
。
在输出的末尾,我们看到了一些成功的消息:
Successfully built 747c4a9481d8
Successfully tagged simple-python-app:latest
这些成功消息中的第一个给出了我们刚刚创建的镜像的 ID (747c4a9481d8
),第二个显示它已经使用我们指定的标签进行了标记 (simple-python-app
)。要查看本地机器上的 Docker 镜像,可以运行 docker image ls
:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
simple-python-app latest 7383e489dd38 16 seconds ago 123MB
python 3.8-slim-buster ec75d34adff9 22 hours ago 113MB
nginx latest 4bb46517cac3 3 weeks ago 133MB
在此输出中,我们可以看到我们刚刚构建的 simple-python-app
镜像。现在我们已经构建了一个容器镜像,可以准备运行它了!
运行镜像
正如我们之前看到的,我们可以使用 docker run
命令运行容器:
$ docker run -d -p 5000:5000 --name chapter-07-example simple-python-app
6082241b112f66f2bb340876864fa1ccf170a 519b983cf539e2d37e4f5d7e4df
在这里,您可以看到我们正在将 simple-python-app
镜像作为名为 chapter-07-example
的容器运行,并且已经暴露了端口 5000
。命令输出显示了我们刚刚启动的容器的 ID。
容器运行时,我们可以在 Web 浏览器中打开http://localhost:5000
:
图 7.5 - 展示在 Web 浏览器中的容器化示例应用程序的屏幕截图
在这个屏幕截图中,我们可以看到示例应用程序的输出。请注意,它输出的主机名与docker run
命令的输出中容器 ID 的开头匹配。当创建容器的隔离环境时,主机名设置为容器 ID 的短格式。
现在我们已经构建和运行了容器的初始版本,让我们来看看如何修改应用程序并重新构建镜像。
使用更改重新构建镜像
在开发应用程序时,我们会对源代码进行更改。为了模拟这个过程,在app.py
中对消息进行简单更改(例如,将Hello from
更改为Coming to you from
)。一旦我们进行了这个更改,我们可以使用与之前相同的docker build
命令重新构建容器镜像:
$ docker build -t simple-python-app -f Dockerfile .
Sending build context to Docker daemon 5.12kB
Step 1/7 : FROM python:3.8-slim-buster
---> 772edcebc686
Step 2/7 : EXPOSE 5000
---> Using cache
---> 3e0273f9830d
Step 3/7 : ADD requirements.txt .
---> Using cache
---> 71180e54daa0
Step 4/7 : RUN python -m pip install -r requirements.txt
---> Using cache
---> c5ab90bcfe94
Step 5/7 : WORKDIR /app
---> Using cache
---> f4a62a82db1a
Step 6/7 : ADD . /app
---> 612bba79f590
Step 7/7 : CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
---> Running in fbc6af76acbf
Removing intermediate container fbc6af76acbf
---> 0dc3b05b193f
Successfully built 0dc3b05b193f
Successfully tagged simple-python-app:latest
这次的输出有点不同。除了基本镜像不被拉取(因为我们已经下载了基本镜像)之外,您还可能注意到一些带有---> Using cache
的行。当 Docker 运行Dockerfile
中的命令时,每一行(有几个例外)都会创建一个新的容器镜像,后续的命令就像我们在基本镜像上构建一样。由于它们相互构建,这些镜像通常被称为层。在构建镜像时,如果 Docker 确定命令中使用的文件与先前构建的层匹配,则它将重用该层,并通过---> Using cache
输出指示此情况。如果文件不匹配,则 Docker 运行该命令并使任何后续层的缓存无效。
这种层缓存是为什么我们将requirements.txt
从应用程序的主要内容中拆分出来放在Dockerfile
中的原因:安装依赖通常是一个耗时的操作,并且通常应用程序的其他文件更频繁地发生变化。将依赖拆分出来,并在复制应用程序代码之前执行pip install
,可以确保层缓存在我们开发应用程序时与我们一起工作。
我们在这里看到了一系列 Docker 命令;如果您想进一步探索(包括如何将镜像推送到注册表),请查看 https://www.docker.com/101-tutorial 上的Docker 101 教程。
在本节中,我们已经看到了如何构建容器镜像以及如何运行容器,无论是我们自己的镜像还是来自 Docker Hub 的镜像。我们还看到了层缓存如何加速开发周期。这些都是基础步骤,在下一节中,我们将开始研究编排器,这是使用容器构建系统的上一层。
介绍编排器
在前一节中,我们看到了如何使用 Docker 的功能将我们的应用程序打包成容器镜像并运行。如果我们将镜像推送到 Docker 注册表,那么从安装了 Docker 的任何机器上都可以简单地拉取和运行该应用程序。然而,较大的系统由许多这样的组件组成,我们可能希望将它们分布在多个 Docker 主机上。这样可以通过增加或减少运行的组件容器实例的数量来适应系统上的负载变化。使用容器化系统获得这些功能的方法是使用编排器。编排器提供其他功能,例如自动重新启动失败的容器,在主机故障时在不同的主机上运行容器,以及与容器进行稳定通信,因为它们可能会重新启动并在主机之间移动。
有许多容器编排器,如 Kubernetes、Docker Swarm 和基于 Apache Mesos 和 Marathon 的 Mesosphere DC/OS。这些编排器都提供了稍微不同的功能和实现我们刚才描述的要求的方式。Kubernetes 已经成为一个非常流行的编排器,所有主要的云供应商都提供了 Kubernetes 的支持(它甚至在 Docker Enterprise 和 Mesosphere DC/OS 中都有支持)。本章的其余部分将介绍如何在 WSL 中创建一个 Kubernetes 开发环境并在其上运行应用程序。
在 WSL 中设置 Kubernetes
安装 Kubernetes 有很多选择,包括以下几种:
-
Kind(
kind.sigs.k8s.io/
) -
MicroK8s(https://microk8s.io/)
-
k3s(
k3s.io/
)
首先是 Kind,它代表 Kubernetes in Docker,专为测试 Kubernetes 而设计。只要您的构建工具可以运行 Docker 容器,它就可以作为在自动化构建中运行 Kubernetes 的一种好选择,用于集成测试的一部分。默认情况下,Kind 将创建一个单节点 Kubernetes 集群,但您可以配置它以运行多节点集群,其中每个节点都作为一个单独的容器运行(我们将在第十章“使用 Visual Studio Code 和容器”中看到如何使用 Kind 在开发容器中使用 Kubernetes)。
然而,在本章中,我们将使用 Docker Desktop 中内置的 Kubernetes 功能,它提供了一种方便的方式来启用由您管理的 Kubernetes 集群。
图 7.6 - 显示在 Docker Desktop 中启用 Kubernetes 的屏幕截图
在这个屏幕截图中,您可以看到 Docker Desktop 设置的 Kubernetes 页面,其中包含“启用 Kubernetes”选项。勾选此选项并点击“应用并重启”,Docker Desktop 将为您安装一个 Kubernetes 集群。
就像我们一直使用docker
CLI 与 Docker 进行交互一样,Kubernetes 也有自己的 CLI,即kubectl
。我们可以使用kubectl cluster-info
命令来检查我们是否能够连接到 Docker Desktop 为我们创建的 Kubernetes 集群:
$ kubectl cluster-info
Kubernetes master is running at https://kubernetes.docker.internal:6443
KubeDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
这个输出显示kubectl
已成功连接到kubernetes.docker.internal
上的 Kubernetes 集群,表示我们正在使用Docker Desktop Kubernetes 集成。
现在我们有一个运行的 Kubernetes 集群,让我们来看看如何在其中运行一个应用程序。
在 Kubernetes 中运行 Web 应用程序
Kubernetes 引入了一些新的术语,其中第一个是 Pod。Pod 是在 Kubernetes 中运行容器的方式。当我们要求 Kubernetes 运行一个 Pod 时,我们会指定一些细节,比如我们希望它运行的镜像。像 Kubernetes 这样的编排器旨在使我们能够作为系统的一部分运行多个组件,包括能够扩展组件实例的数量。为了帮助实现这个目标,Kubernetes 添加了另一个概念,称为 Deployments。Deployments 是基于 Pod 构建的,允许我们指定我们希望 Kubernetes 运行的相应 Pod 的实例数量,并且这个值可以动态更改,使我们能够扩展(和缩小)我们的应用程序。
我们将稍后查看如何创建部署,但首先,我们需要为我们的示例应用程序创建一个新的标签。在之前构建 Docker 镜像时,我们使用了simple-python-app
标签。每个标签都有一个或多个关联的版本,由于我们没有指定版本,它被假定为simple-python-app:latest
。在使用 Kubernetes 时,使用latest镜像版本意味着 Kubernetes 将尝试从注册表中拉取镜像,即使它已经在本地存在。由于我们还没有将镜像推送到注册表,这将失败。我们可以重新构建镜像,指定simple-python-app:v1
作为镜像名称,但由于我们已经构建了镜像,我们也可以通过运行docker tag simple-python-app:latest simple-python-app:v1
来创建一个新的带标签的版本。现在我们有两个引用同一镜像的标签,但是通过使用simple-python-app:v1
标签,只有在本地不存在镜像时,Kubernetes 才会尝试拉取镜像。有了我们的新标签,让我们开始将应用程序部署到 Kubernetes。
创建一个部署
将我们的示例应用程序部署到 Kubernetes 的第一步是在 Kubernetes 中创建一个部署对象。使用我们容器镜像的版本标签,我们可以使用kubectl
创建一个部署:
$ kubectl create deployment chapter-07-example --image=simple-python-app:v1
deployment.apps/chapter-07-example created
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
chapter-07-example 1/1 1 1 10s
此输出显示创建了一个名为chapter-07-example
的部署,运行着simple-python-app:v1
镜像。创建部署后,它显示了使用kubectl get deployments
列出部署并获取关于部署状态的摘要信息。在这里,READY
列中的1/1
表示部署配置为运行一个实例的 pod,并且该实例可用。如果我们 pod 中运行的应用程序崩溃,Kubernetes 将(默认情况下)自动重新启动它。我们可以运行kubectl get pods
来查看部署创建的 pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
chapter-07-example-7dc44b8d94-4lsbr 1/1 Running 0 1m
在这个输出中,我们可以看到 pod 的名称以部署名称开头,后面跟着一个随机后缀。
正如我们之前提到的,使用部署而不是 pod 的一个好处是可以对其进行扩展:
$ kubectl scale deployment chapter-07-example --replicas=2
deployment.apps/chapter-07-example scaled
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
chapter-07-example-7dc44b8d94-4lsbr 1/1 Running 0 2m
chapter-07-example-7dc44b8d94-7nv7j 1/1 Running 0 15s
在这里,我们看到kubectl scale
命令在chapter-07-example
部署上使用,将副本数设置为 2,换句话说,将部署扩展到两个 pod。扩展后,我们再次运行kubectl get pods
,可以看到我们创建了第二个 pod。
提示
在使用 kubectl 时,您可以通过启用 bash 自动补全来提高生产力。要配置这个,请运行:
echo 'source <(kubectl completion bash)' >>~/.bashrc
这将将 kubectl bash 自动补全添加到您的.bashrc
文件中,因此您需要重新启动 Bash 以启用它(有关详细信息,请参阅kubernetes.io/docs/tasks/tools/install-kubectl/#optional-kubectl-configurations
),
通过这个更改,您现在可以键入以下内容(在
kubectl sc<TAB> dep<TAB> chap<TAB> --re<TAB>2
使用 bash 自动补全的最终结果是:
kubectl scale deployment chapter-07-example --replicas=2
正如您所看到的,这样可以节省输入命令的时间,并支持命令(如scale
)和资源名称(chapter-07-example
)的自动补全。
现在我们已经部署了应用程序,让我们看看如何访问它。
创建一个服务
接下来,我们希望能够访问作为chapter-07-example
部署运行的 Web 应用程序。由于我们可以在多个 pod 上运行 Web 应用程序的实例,我们需要一种访问一组 pod 的方法。为此,Kubernetes 有一个称为kubectl expose
的概念来创建服务:
$ kubectl expose deployment chapter-07-example --type="NodePort" --port 5000
service/chapter-07-example exposed
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
chapter-07-example NodePort 10.107.73.156 <none> 5000:30123/TCP 7s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 16m
在这里,我们运行kubectl expose
命令,指示 Kubernetes 为我们的chapter-07-example
部署创建一个服务。我们将服务类型指定为NodePort
,这使得服务在集群中的任何节点上都可用,并将5000
作为服务目标端口,以匹配我们的 Web 应用程序正在侦听的端口。接下来,我们运行kubectl get services
命令,显示新创建的chapter-07-example
服务。在PORT(S)
列下,我们可以看到5000:30123/TCP
,表示该服务正在端口30123
上侦听,并将流量转发到部署中的端口5000
。
由于 Docker Desktop 为 Kubernetes 集群设置了网络(以及从 Windows 到 WSL 的localhost
的 WSL 转发),我们可以在 Web 浏览器中打开http://localhost:30123
。
图 7.7 - 展示在浏览器中加载的 Kubernetes Web 应用程序的屏幕截图
此屏幕截图显示了在浏览器中加载的 Web 应用程序和显示的主机名与我们在缩放部署后列出的 Pod 名称之一相匹配。如果您刷新页面几次,您将看到名称在我们缩放部署后创建的 Pod 名称之间更改,这表明我们创建的 Kubernetes 服务正在在 Pod 之间分发流量。
我们一直在交互式地运行kubectl
命令来创建部署和服务,但 Kubernetes 的一个强大之处是它支持声明式部署。Kubernetes 允许您在YAML
格式的文件中定义部署和服务等对象。通过这种方式,您可以指定系统的多个方面,然后一次性将一组YAML
文件传递给 Kubernetes,Kubernetes 将创建它们。您以后可以更新YAML规范并将其传递给 Kubernetes,它将协调规范中的差异以应用您的更改。本书附带的代码示例位于chapter-07/02-deploy-to-kubernetes
文件夹中(请参阅文件夹中的README.md
文件以获取部署说明)。
在本节中,我们介绍了如何使用 Kubernetes 部署我们打包为容器镜像的 Web 应用程序。我们看到这将为我们创建 Pod,并允许我们动态扩展正在运行的 Pod 的数量。我们还看到如何使用 Kubernetes 创建一个服务,该服务在部署中的 Pod 之间分发流量。该服务为部署中的 Pod 提供了逻辑抽象,并处理部署的扩展以及已重新启动的 Pod(例如,如果它已崩溃)。这为使用 Kubernetes 工作提供了一个很好的起点,如果您想进一步了解,Kubernetes 在kubernetes.io/docs/tutorials/kubernetes-basics/
上有一个很棒的交互式教程。
注意
如果您有兴趣深入了解使用Docker或Kubernetes构建应用程序,以下链接是一个很好的起点(还有其他内容的进一步链接):
总结
在本章中,我们介绍了容器,并看到它们如何使应用程序及其依赖项打包在一起,以便在运行 Docker 守护程序的机器上简单运行。我们讨论了 Docker 注册表作为共享镜像的一种方式,包括常用的公共注册表:docker
CLI,并使用它来运行来自 Docker Hub 的nginx
镜像,Docker 会自动从 Docker Hub 将镜像拉取到本地机器上。
在运行了nginx
镜像之后,你学会了如何使用在Dockerfile
中定义的步骤来构建自定义 Web 应用程序的镜像。你了解到 Docker 会为Dockerfile
中的步骤构建镜像层,并在后续构建中重用它们,如果文件没有更改的话。你还了解到可以通过精心构建Dockerfile
,将最常变化的内容添加到后续步骤中,从而提高后续构建的时间。
在学习如何使用 Docker 之后,你了解了容器编排器的概念,然后开始学习 Kubernetes。通过 Kubernetes,你了解到可以使用不同类型的资源,如 pod、deployment 和 service 来部署应用程序。你了解到 Kubernetes 的部署是基于 pod 构建的,可以通过一个命令轻松地扩展 pod 实例的数量,并且可以使用 Kubernetes 的 service 来提供一种简单且一致的方式来访问部署中的 pod,而不受扩展的影响。
在下一章中,我们将更直接地关注 WSL,掌握构建和使用容器的知识将会很有用。
第八章:使用 WSL 发行版
在第二章中,安装和配置 Windows 子系统 Linux,在介绍 wsl 命令部分,我们看到了如何使用wsl
命令列出我们安装的发行版(distros),在其中运行命令,并根据需要终止它们。
在本章中,我们将重新讨论发行版,这次从发行版管理的角度来看。特别是,我们将看看如何使用export
和import
命令备份发行版或将其复制到另一台机器。我们还将看看如何快速创建一个基于 Docker 容器映像的新发行版,以便您可以轻松创建自己的发行版,并安装任何依赖项。
在本章中,我们将介绍以下主要内容:
-
导出和导入 WSL 发行版
-
创建和运行自定义发行版
我们将从介绍如何导出和导入 WSL 发行版开始本章。
导出和导入 WSL 发行版
如果您花费了时间设置 WSL 发行版,您可能希望能够将其复制到另一台机器上。这可能是因为您正在更换或重新安装计算机,或者您拥有多台计算机,希望将配置好的发行版复制到第二台计算机上,而不是从头开始设置发行版。在本节中,我们将介绍如何将发行版导出为可以复制到另一台机器并导入的存档文件。
让我们首先准备要导出的发行版。
准备导出
在导出发行版之前,我们要确保发行版的默认用户在发行版内的/etc/wsl.conf
文件中设置正确(您可以在第二章中的安装和配置 Windows 子系统 Linux,介绍 wsl.conf 和.wslconfig部分了解更多关于wsl.conf
的信息)。通过这样做,我们可以确保在导入发行版后,WSL 仍然使用正确的默认用户。
在 WSL 发行版中打开终端并运行cat /etc/wsl.conf
以检查文件的内容:
$ cat /etc/wsl.conf
[network]
generateHosts = true
generateResolvConf = true
[user]
default=stuart
在此输出的末尾,您可以看到带有default=stuart
条目的[user]
部分。如果您没有默认用户条目(或者没有wsl.conf
文件),那么您可以使用您喜欢的编辑器确保有一个类似于此的条目(带有正确的用户名)。或者,您可以运行以下命令添加一个用户(假设您的wsl.conf
没有[user]
部分):
sudo bash -c "echo -e '\n[user]\ndefault=$(whoami)' >> /etc/wsl.conf"
此命令使用echo
输出带有默认设置为当前用户的[user]
部分。它嵌入了调用whoami
获取当前用户名的结果。整个命令被包装并使用sudo
执行,以确保具有写入文件所需的权限。
准备工作完成后,让我们看看如何导出发行版。
执行导出
要导出发行版,我们将使用wsl
命令将发行版的内容导出到磁盘上的文件中。为此,我们运行wsl --export
:
wsl --export Ubuntu-18.04 c:\temp\Ubuntu-18.04.tar
正如您所看到的,我们指定了要导出的发行版的名称(Ubuntu-18.04
),然后是我们要保存导出文件的路径(c:\temp\Ubuntu-18.04.tar
)。导出过程将根据发行版的大小和其中的内容量而需要一些时间来完成。
在导出过程中,发行版无法使用,如使用wsl --list
命令(在单独的终端实例中执行)所示:
PS C:\> wsl --list --verbose
NAME STATE VERSION
* Ubuntu-20.04 Running 2
Legacy Stopped 1
Ubuntu-18.04 Converting 2
PS C:\>
在此输出中,您可以看到Ubuntu-18.04
发行版的状态显示为Converting
。一旦导出命令完成,该发行版将处于Stopped
状态。
导出的文件是一个以TAR格式(最初是Tape Archive的缩写)创建的存档文件,这在 Linux 中很常见。如果您打开 TAR 文件(例如,在诸如www.7-zip.org/
的应用程序中),您可以看到其中的内容:
图 8.1 - 展示在 7-zip 中打开的导出的 TAR 的屏幕截图
在此屏幕截图中,您可以看到导出的 TAR 文件包含了一个 Linux 系统的熟悉文件夹。您可以深入到诸如 /home/stuart
的文件夹中,并导出单个文件(如果您希望这样做)。
现在我们有了一个导出的发行版文件,让我们看看如何导入它。
执行导入
一旦您有了发行版的导出文件,您可以将其复制到新机器(假设您正在传输发行版),或者如果您使用导出/导入来创建发行版的副本,则可以将其保留在同一位置。
要执行导入,我们将使用以下 wsl
命令:
wsl --import Ubuntu-18.04-Copy C:\wsl-distros\Ubuntu-18.04-Copy C:\temp\Ubuntu-18.04.tar
正如您所看到的,这次我们使用了 --import
开关。之后,我们传递以下三个参数:
-
Ubuntu-18.04-Copy
:这是将由导入创建的新发行版的名称。 -
C:\wsl-distros\Ubuntu-18.04-Copy
:这是新发行版的状态将存储在磁盘上的路径。通过商店安装的发行版将安装在$env:LOCALAPPDATA\Packages
下的文件夹中,如果您希望将导入的发行版保存在类似位置的路径下,您可以使用此路径。 -
C:\temp\Ubuntu-18.04.tar
:要导入的已导出发行版的 TAR 文件的路径。
与导出一样,如果内容很多,导入过程可能需要一些时间。我们可以通过在另一个终端实例中运行 wsl
来查看状态:
PS C:\ > wsl --list --verbose
NAME STATE VERSION
* Ubuntu-20.04 Running 2
Legacy Stopped 1
Ubuntu-18.04-Copy Installing 2
Ubuntu-18.04 Stopped 2
PS C:\Users\stuar>
在此输出中,我们可以看到新的发行版(Ubuntu-18.04-Copy
)在导入过程中显示为 Installing
状态。一旦 import
命令完成,新的发行版就可以使用了。
正如您在这里看到的,通过将发行版导出为可以导入的 TAR 文件,您可以在您的计算机上创建发行版的副本,例如,测试一些其他应用程序而不影响原始发行版。通过在计算机之间复制 TAR 文件,它还可以让您复制已配置的发行版以便在计算机之间重用它们。
接下来,我们将看看如何创建自己的发行版。
创建和运行自定义发行版
如果您在多个项目中工作,每个项目都有自己的工具集,并且您希望保持依赖关系的分离,那么为每个项目运行一个发行版可能是有吸引力的。我们刚刚看到的导出和导入发行版的技术可以通过复制起始发行版来实现这一点。
在本节中,我们将介绍使用 Docker 镜像的另一种方法。Docker Hub 上发布了大量的镜像,包括安装了各种开发工具集的镜像。正如我们将在本节中看到的,这可以是一种快速安装发行版以使用新工具集的方法。在第十章中,Visual Studio Code 和容器,我们将看到另一种方法,直接使用容器来封装您的开发依赖项。
在开始之前,值得注意的是,还有另一种构建用于 WSL 的自定义发行版的方法,但这是一个更复杂的过程,并且不适用于本节的场景。这也是发布 Linux 发行版到商店的途径 - 详细信息可以在docs.microsoft.com/en-us/windows/wsl/build-custom-distro
找到。
在本节中,我们将介绍如何使用容器设置一个准备好与 .NET Core 一起工作的发行版(但是这个过程适用于任何您可以找到容器镜像的技术栈)。我们将使用 Docker Hub 找到我们想要用作新 WSL 发行版基础的镜像,然后配置一个正在运行的容器,以便它能够与 WSL 无缝配合。一旦我们设置好容器,我们将导出它为一个可以像前一节中所见那样导入的 TAR 文件。
让我们开始找到我们想要使用的镜像。
查找和拉取容器镜像
第一步是找到我们想要用作起点的容器。在 Docker Hub 上搜索dotnet
后(hub.docker.com/
),我们可以向下滚动以找到来自 Microsoft 的镜像,这将引导我们到这个页面(hub.docker.com/_/microsoft-dotnet-core
):
图 8.2 - Docker Hub 上.NET 镜像页面的截图
正如您在这个截图中所看到的,有许多可用的.NET 镜像。在本章中,我们将使用.NET 5.0 镜像,特别是 SDK 镜像,因为我们希望能够测试构建应用程序(而不仅仅是运行为运行时镜像设计的应用程序)。
通过点击dotnet/sdk
页面,我们可以找到我们需要使用的镜像标签来拉取和运行镜像:
图 8.3 - Docker Hub 上显示.NET 5.0 SDK 镜像标签的截图
正如这个截图所示,我们可以运行docker pull mcr.microsoft.com/dotnet/sdk:5.0
将镜像拉取到我们的本地机器上。
现在我们已经找到了要用作新发行版起点的镜像,接下来有几个步骤来准备它以便与 WSL 一起使用。让我们看看这些步骤是什么。
配置一个准备用于 WSL 的容器
在我们可以导出刚从 Docker Hub 拉取的镜像之前,我们需要进行一些调整,以使其与 WSL 完全兼容:
- 首先,我们将从镜像创建一个正在运行的容器:
dotnet to make it easier to refer to it later. We also passed the -it switches to start the container with interactive access – note the final line in the previous output showing that we're at a shell prompt inside the container.
- 首先要设置的是 WSL 要使用的用户:
useradd command to create a new user called stuart (but feel free to pick a different name!) and the -m switch ensures that the user home directory is created. After that, we use the passwd command to set a password for the user.
- 接下来,我们将添加
/etc/wsl.conf
文件以告诉 WSL 使用我们刚创建的用户:
echo command to set the file content, but you can use your favorite terminal text editor if you prefer. After writing the file, we dump it out to show the contents – be sure to set the value of the default property to match the user you created here.
在这个阶段,我们可以进行额外的配置(我们将在本章后面的“进一步操作”部分中看到一些示例),但是现在基本的准备工作已经完成,所以让我们将容器转换为 WSL 发行版。
将容器转换为 WSL 发行版
在本章的第一节中,我们看到了如何将 WSL 发行版导出为 TAR 文件,然后将该 TAR 文件作为新的发行版导入(在同一台或不同的机器上)。
幸运的是,Docker 提供了一种将容器导出为与 WSL 使用的格式兼容的 TAR 文件的方法。在本节中,我们将采用刚刚配置的容器,并使用导出/导入过程将其转换为 WSL 发行版。
在导出之前,让我们退出容器:
root@62bdd6b50070:/# exit
exit
PS C:\> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
62bdd6b50070 mcr.microsoft.com/dotnet/sdk:5.0 "bash" 52 minutes ago Exited (0) 7 seconds ago dotnet
这个输出显示了运行exit
命令以退出容器中的bash
实例。这会导致容器进程退出,容器不再运行。通过运行docker ps -a
,我们可以看到所有容器的列表(包括已停止的容器),并且我们可以看到我们一直在使用的容器被列出。
接下来,我们可以将 Docker 容器导出为一个 TAR 文件:
docker export -o c:\temp\dotnet.tar dotnet
在这里,我们使用docker export
命令。-o
开关提供输出 TAR 文件的路径,最后一个参数是我们要导出的容器的名称(dotnet
)。
一旦这个命令完成(可能需要一些时间),我们就可以使用wsl
命令导入准备好的 TAR 文件:
wsl --import dotnet5 C:\wsl-distros\dotnet5 C:\temp\dotnet.tar --version 2
import
命令与前面的部分相同。第一个参数是我们要创建的发行版的名称,dotnet5
;第二个参数指定 WSL 应该存储发行版的位置;最后,我们给出要导入的 TAR 文件的路径。
完成后,我们创建了一个新的 WSL 发行版,准备运行它。
运行新的发行版
现在我们已经创建了一个新的发行版,我们可以进行测试。让我们在发行版中启动一个新的bash
实例,并检查我们正在以哪个用户身份运行:
PS C:\> wsl -d dotnet5 bash
stuart@wfhome:/mnt/c$ whoami
stuart
stuart@wfhome:/mnt/c$
在这里,我们在刚刚创建的dotnet5
发行版中启动bash
,并运行whoami
。这表明我们正在以我们在导入发行版之前在容器中创建和配置的stuart
用户身份运行。
现在我们可以测试运行dotnet
:
- 首先,让我们用
dotnet new
创建一个新的 Web 应用程序:
stuart@wfhome:~$ dotnet new webapp --name new-web-app
The template "ASP.NET Core Web App" was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/aspnetcore/5.0-third-party-notices for details.
Processing post-creation actions...
Running 'dotnet restore' on new-web-app/new-web-app.csproj...
Determining projects to restore...
Restored /home/stuart/new-web-app/new-web-app.csproj (in 297 ms).
Restore succeeded.
- 接下来,我们可以切换到新的 Web 应用程序目录,并使用
dotnet run
运行它:
stuart@wfhome:~$ cd new-web-app/
stuart@wfhome:~/new-web-app$ dotnet run
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {d4a5da2e-44d5-4bf7-b8c9-ae871b0cdc42} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: /home/stuart/new-web-app
^Cinfo: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
正如您所看到的,这种方法为我们提供了一种快速创建新的、独立的 WSL 发行版的好方法,这可以用来在项目之间拆分不同的依赖关系。这种方法还可以用来创建临时发行版,以尝试预览而不在主要发行版中安装它们。在这种情况下,您可以使用wsl --unregister dotnet5
来删除发行版,并释放磁盘空间。
我们在这里使用的过程要求我们交互地执行一些步骤,在许多情况下这是可以接受的。如果您发现自己重复执行这些步骤,您可能希望将它们更加自动化,我们将在下一步中看看如何做到这一点。
进一步的步骤
到目前为止,我们已经看到了如何使用 Docker 交互式地设置一个可以导出为 TAR 文件并作为 WSL 发行版导入的容器。在本节中,我们将看看如何自动化这个过程,并作为自动化的一部分,我们将添加一些额外的步骤来完善之前执行的镜像准备工作。
容器配置自动化的基础是我们在第七章中看到的Dockerfile
,即在在 WSL 中使用容器一节中的介绍 Dockerfile部分。我们可以使用Dockerfile
来构建镜像,然后我们可以按照之前的步骤运行一个容器从镜像中导出文件系统到一个可以作为 WSL 发行版导入的 TAR 文件中。
让我们从Dockerfile
开始。
创建 Dockerfile
docker build
命令允许我们传递一个Dockerfile
来自动化构建容器镜像的步骤。这里显示了一个Dockerfile
的起点:
FROM mcr.microsoft.com/dotnet/sdk:5.0
ARG USERNAME
ARG PASSWORD
RUN useradd -m ${USERNAME}
RUN bash -c 'echo -e "${PASSWORD}\n${PASSWORD}\n" | passwd ${USERNAME}'
RUN bash -c 'echo -e "[user]\ndefault=${USERNAME}" > /etc/wsl.conf'
RUN usermod -aG sudo ${USERNAME}
RUN apt-get update && apt-get -y install sudo
在这个Dockerfile
中,我们在FROM
步骤中指定了起始镜像(之前使用的dotnet/sdk
镜像),然后使用了一些ARG
语句来允许传递USERNAME
和PASSWORD
。之后,我们使用RUN
运行了一系列命令来配置镜像。通常,在一个Dockerfile
中,您会看到这些命令被连接为一个单独的RUN
步骤,以帮助减少层数和大小,但在这里,我们只是要导出完整的文件系统,所以无所谓。让我们看一下这些命令:
-
我们有
useradd
,之前我们用它来创建用户,这里我们使用它和USERNAME
参数值。 -
passwd
命令要求用户输入密码两次,所以我们使用echo
在两次密码之间输出一个换行,并将其传递给passwd
。我们调用bash
来运行这个命令,这样我们就可以使用\n
来转义换行符。 -
我们再次使用
echo
来将/etc/wsl.conf
的内容设置为配置 WSL 的默认用户。 -
我们调用
usermod
将用户添加到sudo
ers 组,以允许用户运行sudo
。 -
然后,我们使用
apt-get
来安装sudo
实用程序。
正如您所看到的,这个列表涵盖了我们之前手动运行的步骤以及一些其他步骤,以设置sudo
以使环境感觉更加自然。您可以在这里添加任何其他步骤,并且这个Dockerfile
可以通过更改FROM
镜像来重用于其他基于 Debian 的镜像。
接下来,我们可以使用 Docker 从Dockerfile
构建一个镜像。
创建 TAR 文件
现在我们有了一个Dockerfile
,我们需要调用 Docker 来构建镜像并创建 TAR 文件。我们可以使用以下命令来完成这个过程:
docker build -t dotnet-test -f Dockerfile --build-arg USERNAME=stuart --build-arg PASSWORD=ticONUDavE .
docker run --name dotnet-test-instance dotnet-test
docker export -o c:\temp\chapter-08-dotnet.tar dotnet-test-instance
docker rm dotnet-test-instance
这组命令执行了从Dockerfile
创建 TAR 文件所需的步骤:
-
运行
docker build
,指定要创建的镜像名称(dotnet-test
),输入的Dockerfile
,以及我们定义的每个ARG
的值。在这里,您可以设置要使用的用户名和密码。 -
使用
docker run
从镜像创建一个容器。我们必须这样做才能导出容器文件系统。Docker 确实有一个save
命令,但它保存的是包含层的完整镜像,而这不是我们需要导入到 WSL 的格式。 -
运行
docker export
将容器文件系统导出为一个 TAR 文件。 -
使用
docker rm
删除容器以释放空间并使重新运行命令变得容易。
此时,我们已经有了 TAR 文件,我们可以像在前一节中看到的那样运行wsl --import
来创建我们的新 WSL 发行版:
wsl --import chapter-08-dotnet c:\wsl-distros\chapter-08-dotnet c:\temp\chapter-08-dotnet.tar
这将创建一个名为chapter-08-dotnet
的发行版,其中包含我们在Dockerfile
中应用的指定用户和配置。
有了这些可脚本化的命令,创建新的发行版变得很容易。您可以在Dockerfile
中添加步骤来添加其他应用程序或配置。例如,如果您打算在该发行版中使用 Azure,您可能希望通过将以下行添加到您的Dockerfile
来方便地安装 Azure CLI:
RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash
这个RUN
命令是基于 Azure CLI 文档中的安装说明(docs.microsoft.com/en-us/cli/azure/install-azure-cli-apt?view=azure-cli-latest
)。
通过这种方式,您可以轻松地脚本化创建根据您需求配置的新 WSL 发行版。无论您计划长时间保留它们还是将它们视为临时的可丢弃环境,这都是您工具包中的强大工具。
总结
在本章中,您已经了解了如何使用 WSL 的export
和import
命令。这些命令允许您将发行版复制到其他计算机,或在重新安装计算机时备份和恢复发行版。它们还提供了一种克隆发行版的方法,如果您想要进行实验或在副本中工作而不影响原始发行版。
您还看到了如何使用容器构建新的发行版。这提供了一种有效的方式来设置新的发行版,以便在其中工作,或者快速测试应用程序而不影响原始发行版。如果您在项目之间具有不同的技术堆栈,并且希望在它们的依赖之间有一些隔离,那么这也可以是设置每个项目发行版的好方法。能够以脚本方式创建这些发行版有助于提高生产力,如果您发现自己使用这种多发行版方法。
随着我们通过使用 Dockerfile 脚本化创建这些环境的进展,我们越来越接近与容器一起工作。我们将在第十章中探索如何继续这个旅程,并直接使用容器进行开发工作,Visual Studio Code 和容器。
在此之前,下一章将介绍 Visual Studio Code,这是一款功能强大且免费的微软编辑器,并探索它如何允许我们在 WSL 中处理源代码。
第三部分:使用 Windows 子系统进行 Linux 开发
本节首先探索了 Visual Studio Code 在 WSL 发行版中为您处理代码提供的强大功能。您还将了解到 Visual Studio Code 如何允许您在 WSL 中使用容器构建隔离且易于共享的容器化开发环境。最后,我们将介绍一些在命令行实用程序中处理 JSON 的技巧以及一些 Azure 和 Kubernetes 命令行工具的技巧。
本节包括以下章节:
第九章,Visual Studio Code 和 WSL
第十章,Visual Studio Code 和容器
第十一章,使用命令行工具的生产力技巧
第九章:Visual Studio Code 和 WSL
到目前为止,本书的重点一直是 WSL 和直接使用 WSL 进行工作。在本章中,我们将提升一个层次,开始探讨在开发应用程序时如何在 WSL 之上工作。特别是在本章中,我们将探索微软提供的免费编辑器 Visual Studio Code。
我们已经看到 WSL 的互操作性允许我们从 Windows 访问 WSL 分发中的文件。Visual Studio Code 允许我们更深入地进行操作,通过在 Windows 中连接到运行在 WSL 分发中的支持编辑器服务,实现图形化编辑体验。通过这种方式,Visual Studio Code 为我们提供了一些能力,例如在 WSL 中运行的 Linux 应用程序的图形化调试体验。这使我们能够在 Visual Studio Code 中保持丰富的基于 Windows 的编辑体验的同时,与 WSL 中的工具和依赖项一起工作。
在本章中,我们将介绍以下主要内容:
-
介绍 Visual Studio Code
-
介绍 Visual Studio Code Remote
-
使用 Remote-WSL 的工作提示
我们将从介绍 Visual Studio Code 并安装它开始本章。
介绍 Visual Studio Code
Visual Studio Code是微软提供的一个免费、跨平台、开源的代码编辑器。它默认支持 JavaScript(和 TypeScript)应用程序,但可以通过扩展支持各种语言(包括 C++、Java、PHP、Python、Go、C#和 SQL)。让我们开始安装 Visual Studio Code。
要安装 Visual Studio Code,请访问code.visualstudio.com/
,点击下载链接,并在下载完成后运行安装程序。安装过程相当简单,但如果你想要更多详细信息(包括如何安装 Insiders 版本,提供每夜构建),请参阅code.visualstudio.com/docs/setup/setup-overview
。
安装完成后,启动 Visual Studio Code 将呈现如下窗口:
图 9.1 - Visual Studio Code 的截图
在这个截图中,你可以看到 Visual Studio Code 中的欢迎页面。该页面提供了一些常见操作的链接(如打开文件夹),最近打开的文件夹(在首次安装时没有这些),以及各种有用的帮助页面。
总的来说,使用 Visual Studio Code 的基本用法可能会让人感到熟悉,与其他图形化编辑器类似。文档中有一些很好的入门视频(code.visualstudio.com/docs/getstarted/introvideos
)以及书面的技巧和技巧(code.visualstudio.com/docs/getstarted/tips-and-tricks
)。这些链接提供了许多有用的技巧,可以帮助你充分利用 Visual Studio Code,并推荐提高你的工作效率。
有多种选项可以打开一个文件夹开始工作:
-
在欢迎页面上使用打开文件夹...链接,如图 9.1所示。
-
在文件菜单中使用打开文件夹...选项。
-
在命令面板中使用文件:打开文件夹...选项。
这里的最后一个选项,使用命令面板,是一个强大的选项,因为它提供了在 Visual Studio Code 中快速搜索任何命令的方法。你可以通过按下Ctrl + Shift + P来访问命令面板:
图 9.2 - 显示命令面板的截图
此截图显示了命令面板打开的情况。命令面板提供对 Visual Studio Code 中所有命令(包括已安装扩展的命令)的访问。在命令面板中输入时,操作列表会被过滤。在此截图中,您可以看到我已经过滤了“文件打开”,这样可以快速访问“文件:打开文件夹…”操作。值得注意的是,命令面板还显示了命令的键盘快捷键,为学习常用命令的快捷方式提供了一种简单的方法。
如前所述,Visual Studio Code 有各种各样的扩展,可以在 https://marketplace.visualstudio.com/vscode 上浏览,或者您可以从命令面板中选择Extensions: Install Extensions来直接在 Visual Studio Code 中浏览和安装。扩展可以为 Visual Studio Code 添加功能,包括支持新的语言,提供新的编辑器主题或添加新的功能。在本章的示例中,我们将使用一个 Python 应用程序,但这些原则也适用于其他语言。要了解如何添加语言支持的更多信息,请参阅code.visualstudio.com/docs/languages/overview
。
在我们开始查看示例应用程序之前,让我们先看一下一个为 Visual Studio Code 添加了丰富的 WSL 支持的扩展。
介绍 Visual Studio Code Remote
从 WSL 发行版的文件系统中处理文件的一种方法是使用 WSL 提供的\\wsl$
共享(如第四章中所讨论的Windows 与 Linux 的互操作性中的从 Windows 访问 Linux 文件部分)。例如,我可以从\\wsl$\Ubuntu-20.04\home\stuart\wsl-book
中的我的主目录访问wsl-book
文件夹。然而,尽管这样可以工作,但它会产生 Windows 到 Linux 文件互操作的成本,并且不能为我提供一个集成的环境。
在 Windows 上,如果我们安装了 Python 以及 Visual Studio Code 的 Python 扩展,那么我们可以获得一个集成的体验来运行和调试我们的代码。如果我们通过\\wsl$
共享打开代码,那么 Visual Studio Code 仍然会给我们提供 Windows 体验,而不是使用 WSL 中 Python 及其依赖和工具的安装。然而,通过 Microsoft 的Remote-WSL 扩展,我们可以解决这个问题!
通过 Remote Development 扩展,Visual Studio Code 现在将体验分为 Visual Studio Code 用户界面和 Visual Studio Code 服务器。服务器部分负责加载源代码,启动应用程序,运行调试器,启动终端进程等其他活动。用户界面部分通过与服务器通信提供 Windows 用户界面功能。
远程扩展有各种不同的版本:
-
Remote-WSL,在 WSL 中运行服务器
-
Remote-SSH,允许您通过 SSH 连接到远程机器来运行服务器
-
Remote-Containers,允许您使用容器来运行服务器
我们将在本章的其余部分介绍 Remote-WSL,下一章将介绍 Remote-Containers。有关 Remote-Development 扩展的更多信息(包括 Remote-SSH),请参阅 https://code.visualstudio.com/docs/remote/remote-overview。让我们开始使用 Remote-WSL。
开始使用 Remote-WSL
Remote-WSL 扩展包含在 Remote-Development 扩展包中(marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack
),它提供了一种简单的方式来一键安装 Remote-WSL、Remote-SSH 和 Remote-Containers。如果你只想安装 Remote-WSL,请在这里进行安装:marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl
。
要跟随本书进行操作,请确保在 Linux 发行版中克隆了本书的代码。你可以在github.com/PacktPublishing/Windows-Subsystem-for-Linux-2-WSL-2-Tips-Tricks-and-Techniques
找到代码。
示例代码使用 Python 3,如果你使用的是最新版本的 Ubuntu,它应该已经安装好了。你可以通过在 Linux 发行版中运行python3 -c 'print("hello")'
来测试是否安装了 Python 3。如果命令成功完成,则说明一切准备就绪。如果没有,请参考 Python 文档中的安装说明:wiki.python.org/moin/BeginnersGuide/Download
。
现在让我们在 Visual Studio Code 中打开示例代码。
使用 Remote-WSL 打开文件夹
安装完 Remote-WSL 后,打开 Visual Studio Code 并从命令面板(Ctrl + Shift + P)选择Remote-WSL: New Window:
图 9.3 - 显示命令面板中的 Remote-WSL 命令的屏幕截图
此屏幕截图显示 Remote-WSL 扩展添加的新命令,选择Remote-WSL: New Window。这将打开一个新的 Visual Studio Code 窗口,在默认的 WSL 发行版中启动 Visual Studio Code 服务器并连接到它。如果你想选择连接的发行版,请选择Remote-WSL: New Window using Distro…选项。
新的 Visual Studio Code 窗口打开后,窗口的左下角将显示WSL: Ubuntu-18.04(或者你打开的其他发行版),表示此实例的 Visual Studio Code 通过 Remote-WSL 连接。
现在,我们可以从命令面板中选择File: Open Folder…来打开示例代码。在没有通过 Remote-WSL 连接时,在 Visual Studio Code 中执行此操作将打开标准的 Windows 文件对话框。然而,由于我们通过 Remote-WSL 连接,这个命令现在会提示我们选择连接的发行版中的一个文件夹:
图 9.4 - 显示 Remote-WSL 文件夹选择器的屏幕截图
此屏幕截图显示从 WSL 分发文件系统中选择要打开的文件夹。请注意,我将本书的代码克隆到了home
文件夹中的wsl-book
中。根据你保存代码的位置,你可能会有一个类似于/home/<your-user>/WSL-2-Tips-Tricks-and-Techniques/chapter-09/web-app
的路径。打开文件夹后,Visual Studio 开始处理内容,并提示你安装推荐的扩展(如果你还没有安装 Python 扩展):
图 9.5 - 显示推荐扩展提示的屏幕截图
此屏幕截图中的提示出现是因为您刚刚打开的文件夹包含一个列出 Python 扩展的.vscode/extensions.json
文件。当提示出现时,要么单击Install All安装扩展,要么单击Show Recommendations在安装之前检查扩展。请注意,即使您之前在使用 Remote-WSL 之前已在 Visual Studio Code 中安装了 Python 扩展,您也可能会收到提示:
图 9.6 - 显示在 Windows 中安装了 Python 但未安装 WSL 的屏幕截图
此屏幕截图显示了 Visual Studio Code 中的EXTENSIONS视图,指示 Python 扩展已在 Windows 中安装,并提示我们安装当前项目所加载的 WSL 发行版的 Remote-WSL。如果您看到此提示,请单击Install按钮以在 WSL 中安装。
此时,我们在 Windows 中运行 Visual Studio Code 用户界面,并连接到在我们的 WSL 发行版中运行的服务器组件。服务器已加载了 Web 应用程序的代码,并且我们已安装了 Python 扩展,该扩展现在在服务器中运行。
有了这个设置,让我们看看如何在调试器下运行代码。
运行应用程序
要运行应用程序,我们首先需要确保 Python 扩展正在使用正确的 Python 版本(我们想要 Python 3)。为此,请查看 Visual Studio Code 窗口底部的状态栏,直到看到类似于Python 2.7.18 64 位的内容。单击此部分会弹出 Python 版本选择器:
图 9.7 - 显示 Python 版本选择器的屏幕截图
如此屏幕截图所示,版本选择器显示它检测到的任何 Python 版本,并允许您选择您想要的版本(在这里,我们选择了 Python 3 版本)。请注意,此列表中显示的路径都是 Linux 路径,确认 Python 扩展正在 WSL 中的 Visual Studio Code 服务器中运行。如果您喜欢使用 Python 虚拟环境(docs.python.org/3/library/venv.html
)并为项目创建了一个虚拟环境,这些虚拟环境也会显示在此列表中供您选择。
在运行应用程序之前,我们需要安装依赖项。从命令面板中选择pip3 install -r requirements.txt
以安装我们的依赖项。
提示
如果您尚未安装 pip3,请运行sudo apt-update && sudo apt install python3-pip
进行安装。
或者,按照此处的说明进行操作:packaging.python.org/guides/installing-using-linux-tools/
。
接下来,从app.py
打开app.py
,我们可以通过按下F5来启动调试器,这将提示您选择要使用的配置:
图 9.8 - 显示 Python 配置选择器的屏幕截图
此屏幕截图显示了 Python 扩展允许您选择的一组常见调试选项。我们将在稍后看到如何配置它以实现完全灵活性,但现在选择Flask。这将使用 Flask 框架启动应用程序并附加调试器:
图 9.9 - 显示在调试器下运行应用程序的屏幕截图
在上一个屏幕截图中,您可以看到已打开集成终端窗口,并且 Visual Studio Code 已启动了我们的 Flask 应用程序。当应用程序启动时,它会输出它正在侦听的 URL(在此示例中为http://127.0.0.1:5000
)。将光标悬停在此链接上会提示您使用Ctrl + 单击打开链接。这样做将在默认浏览器中打开 URL:
图 9.10-浏览器中显示 Web 应用程序的屏幕截图
此屏幕截图显示了浏览器中 Web 应用程序的输出,其中包括 Web 应用程序服务器正在运行的操作系统名称和内核版本。同样,这证明了虽然 Visual Studio Code 用户界面在 Windows 中运行,但所有代码都在我们的 WSL 分发中处理和运行。Visual Studio Code 的 Remote-WSL 和 WSL 用于本地主机地址的流量转发的组合为我们提供了跨 Windows 和 Linux 的丰富和自然的体验。
到目前为止,我们只是将调试器用作启动应用程序的便捷方式。接下来,让我们看看如何使用调试器逐步执行代码。
调试我们的应用程序
在本节中,我们将介绍如何在调试器中逐步查看项目中的代码。同样,这使我们可以使用 Windows 中的 Visual Studio Code 用户界面连接到和调试在 WSL 分发中运行的应用程序。
在上一节中,我们看到了如何使用F5运行 Python 应用程序,并提示我们选择要使用的配置(我们选择了Flask)。由于我们还没有为项目配置调试器,因此每次都会提示我们选择环境。在深入研究调试器之前,让我们设置配置,以便F5自动正确启动我们的应用程序。为此,请打开RUN视图,可以通过按下Ctrl + Shift + D或从命令面板中选择Run: Focus on Run View命令来打开:
图 9.11-Visual Studio Code 中显示运行视图的屏幕截图
此屏幕截图显示了launch.json
文件。您将收到与图 9.7中相同的一组选项,并且应再次选择我们打开的文件夹中的.vscode/launch.json
文件:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Flask",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app.py",
"FLASK_ENV": "development",
"FLASK_DEBUG": "0"
},
"args": [
"run",
"--no-debugger",
"--no-reload"
],
"jinja": true
}
]
}
如此内容所示,launch.json
包含一个env
属性。
配置了调试选项后,让我们切换回app.py
文件并设置一个断点。在app.py
中,我们有一个home
方法,它返回一些 HTML 并包含get_os_info
函数的输出。在该函数的return
语句处导航并按下F9添加一个断点(还有其他方法可以做到这一点-请参阅 https://code.visualstudio.com/docs/editor/debugging)。现在,我们可以按下F5运行我们的应用程序,当它处理请求时,它将在调试器中暂停。要触发断点,请像之前一样打开浏览器并切换回 Visual Studio Code:
图 9.12-Visual Studio Code 在 WSL 中调试 Python 应用程序的屏幕截图
此屏幕截图显示了 Visual Studio Code 调试我们的应用程序。在左侧,我们可以看到局部变量(例如,sysname
变量的内容)和调用堆栈。我们可以使用窗口顶部的控件(或它们的键盘快捷键)来恢复执行或逐步执行代码。窗口底部显示了用于运行应用程序的终端,我们可以将其切换到sysname="Hello"
,然后按下F5恢复应用程序。切换回浏览器,您将在浏览器的输出中看到Hello
,显示我们在调试器中更新了变量的值。
在这里,我们看到了 Visual Studio Code 对多种语言的丰富支持(通过安装语言支持扩展)。通过安装和使用Remote-WSL扩展,我们可以在 Windows 中获得 Visual Studio Code 的丰富功能,并在 WSL 中执行所有代码服务。在这个例子中,我们演示了在 WSL 中运行的所有代码服务:Python 解释器、语言服务以实现重构、调试器和正在调试的应用程序。所有这些执行都发生在 WSL 中,因此我们可以在 Linux 中设置环境,然后在开发应用程序时在其上方拥有丰富的用户界面。
现在我们已经了解了核心体验,我们将深入了解一些使用 Remote-WSL 的技巧。
使用 Remote-WSL 的技巧
本节将介绍一些技巧,可以帮助您在使用 Visual Studio Code 和 Remote-WSL 时进一步优化您的体验。
从终端加载 Visual Studio Code
在 Windows 中,您可以使用code <路径>
命令从终端启动 Visual Studio Code,以打开指定的路径。例如,您可以使用code .
来打开当前文件夹(.
)在 Visual Studio Code 中。实际上,这使用了一个code.cmd
脚本文件,但 Windows 允许您省略扩展名。
在使用 WSL 时,通常会打开一个终端,并且使用 Remote-WSL,您还可以获得一个code
命令。因此,您可以在 WSL 的终端中导航到项目文件夹并运行code .
,它将启动 Visual Studio Code 并使用 Remote-WSL 扩展打开指定的文件夹(在这种情况下是当前文件夹)。这种集成是一个很好的选择,可以在 Windows 和 WSL 环境之间保持一致和集成。
在这里,我们看到了如何从终端进入 Visual Studio Code。接下来,我们将看相反的情况。
在 Windows 终端中打开外部终端
有时候你在 Visual Studio Code 中工作,想要一个新的终端来运行一些命令。Visual Studio Code 在 Visual Studio Code 扩展视图中有Windows 终端集成
,或者打开 https://marketplace.visualstudio.com/items?itemName=Tyriar.windows-terminal。安装完成后,会有一些新的命令可用:
图 9.13 - 展示新的 Windows 终端命令的截图
这个截图展示了命令面板中的新命令。打开命令使用 Windows 终端中的默认配置打开 Visual Studio Code 工作区文件夹。打开活动文件夹命令在默认配置中打开包含当前打开文件的文件夹。另外两个命令使用配置文件打开对应于前面的命令,但允许您选择使用哪个 Windows 终端配置文件打开路径。
除了从命令面板中访问的命令外,该扩展还为资源管理器视图中的文件和文件夹添加了右键菜单的新项目:
图 9.14 - 展示右键菜单命令的截图
在这个截图中,我在资源管理器视图中点击了一个文件夹,扩展添加了两个菜单项,用于在 Windows 终端中打开路径。其中第一个菜单项在默认配置中打开路径,第二个菜单项会提示打开路径。
这个扩展可以快速方便地在 Visual Studio Code 项目的上下文中打开一个 Windows 终端实例,让您保持流畅和高效。
接下来,我们将介绍一些使用 Git 的技巧。
使用 Visual Studio Code 作为您的 Git 编辑器
Visual Studio Code 提供了与 Git 存储库一起工作的集成可视化工具。根据个人喜好,您可以使用git
命令行工具来进行一些或全部的 Git 交互。对于某些操作,Git 会打开一个临时文件以获取进一步的输入,例如在合并提交上获取提交消息或确定在交互式 rebase 上采取哪些操作。
除非您配置了其他编辑器,否则 Git 将使用vi
作为其默认编辑器。如果您熟悉vi
,那很好,但如果您更喜欢使用 Visual Studio Code,我们可以利用本章前面看到的code
命令。
要配置 Git 使用 Visual Studio Code,我们可以运行git config --global core.editor "code --wait"
。--global
开关设置所有存储库的配置值(除非它们覆盖它),我们正在设置core.editor
值,该值控制git
使用的编辑器。我们为此设置分配的值是code --wait
,它使用我们在上一节中看到的code
命令。运行code
命令而不使用--wait
开关会启动 Visual Studio Code 然后退出(保持 Visual Studio Code 运行),这通常是在使用它打开文件或文件夹时所希望的行为。但是,当git
启动编辑器时,它期望进程阻塞直到文件关闭,而--wait
开关提供了这种行为:
图 9.15 - 显示 Visual Studio Code 作为 WSL 的 Git 编辑器的屏幕截图
在这个屏幕截图中,您可以在底部的终端中看到一个交互式的git rebase
命令,以及在配置了 Git 编辑器后加载到 Visual Studio Code 中的git-rebase-todo
文件,用于捕获操作。
接下来,我们将继续查看 Git,探索查看 Git 历史记录的方法。
查看 Git 历史记录
在使用 Git 进行版本控制的项目中工作时,您可能会想要在某个时候查看提交历史记录。有各种方法可以实现这一点,您可能也有自己首选的工具。尽管界面风格简单,但我经常使用gitk
,因为它是普遍存在的,作为 Git 安装的一部分包含在其中。在 Windows 上工作时,您可以直接从 Git 存储库的文件夹中运行gitk
。在 WSL 中,我们需要运行gitk.exe
以便启动 Windows 应用程序(请注意,这需要在 Windows 上安装 Git):
图 9.16 - 显示从 WSL 运行的 gitk.exe 的屏幕截图
在这个屏幕截图中,您可以看到从 WSL Git 存储库运行的gitk
Windows 应用程序,并通过文件系统映射访问内容。如果您有其他首选的用于查看 Git 历史记录的 Windows 应用程序,那么这种方法也可以工作,只要该应用程序在您的路径中。如果在运行这些命令时忘记添加.exe
,您可能希望查看第五章,Linux 到 Windows 的互操作性,为 Windows 应用程序创建别名部分。
由于 Windows 应用程序通过\\wsl$
共享使用 Windows 到 Linux 文件映射,您可能会注意到对于大型 Git 存储库,应用程序加载速度较慢,因为这种映射的开销较大。另一种方法是在 Visual Studio Code 中使用扩展,例如Git Graph(https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph):
图 9.17 - 显示 Visual Studio Code 中的 Git Graph 扩展
这个截图显示了使用Git Graph扩展查看的 Git 历史记录。通过使用 Visual Studio Code 扩展来渲染 Git 历史记录,扩展可以由在 WSL 中运行的服务器组件来运行。这样可以直接访问文件来查询 Git 历史记录,并避免了 Windows 应用程序的性能开销。
概述
在本章中,您已经对 Visual Studio Code 有了一个概述,并且看到它是一个灵活的编辑器,具有丰富的扩展生态系统,为各种语言提供支持,并为编辑器添加额外的功能。
其中一个扩展是 Remote-WSL,它允许将编辑器分为两部分,用户界面部分在 Windows 中运行,其他功能在 WSL 中运行(包括文件访问、语言服务和调试器)。
这个功能使您能够无缝地使用 Visual Studio Code 的丰富功能(包括扩展),但是您的源代码和应用程序都在 WSL 中运行。通过这种方式,您可以充分利用适用于您的 WSL 发行版的工具和库。
在下一章中,我们将探索另一个 Visual Studio Code Remote 扩展,这次将研究在容器中运行服务以自动化开发环境并提供依赖项的隔离。
第十章:Visual Studio Code 和容器
在第九章,Visual Studio Code 和 WSL中,我们看到 Visual Studio Code 编辑器允许将用户界面与与我们的代码交互和运行代码的其他功能分离。通过 WSL,这使我们可以在运行我们项目的所有关键部分的 Linux 中保持熟悉的基于 Windows 的用户界面。除了允许代码交互在 WSL 中的服务器组件中运行外,Visual Studio Code 还允许我们通过 SSH 连接到代码服务器或在容器中运行它。能够在容器中运行是由Remote-Containers扩展提供的,本章将重点介绍如何使用此功能。我们将看到如何使用这些开发容器(或dev container)来封装我们的项目依赖项。通过这样做,我们可以更容易地将人们引入我们的项目,并获得一种优雅的方式来隔离可能发生冲突的工具集。
在本章中,我们将介绍以下主要内容:
-
介绍 Visual Studio Code Remote-Containers
-
安装 Remote-Containers
-
创建一个 dev 容器
-
在开发容器中使用容器化应用程序
-
在开发容器中使用 Kubernetes
-
使用开发容器的技巧
在本章中,您需要安装 Visual Studio Code - 请参阅第九章,Visual Studio Code 和 WSL,介绍 Visual Studio Code部分了解更多详细信息。我们将通过介绍 Visual Studio Code 的 Remote-Containers 扩展并将其安装来开始本章。
介绍 Visual Studio Code Remote-Containers
Visual Studio Code 的 Remote-Containers 扩展作为 Remote-Development 扩展包的一部分,与Remote-WSL和Remote-SSH一起。所有这些扩展都允许您将用户界面方面与代码交互分离,例如加载、运行和调试代码。通过 Remote-Containers,我们指示 Visual Studio Code 在我们在Dockerfile中定义的容器内运行这些代码交互(请参阅第七章,在 WSL 中使用容器,介绍 Dockerfiles部分)。
当 Visual Studio Code 在开发容器中加载我们的项目时,它经过以下步骤:
-
从 Dockerfile 构建容器镜像
-
使用生成的镜像运行容器,将源代码挂载到容器中。
-
在容器中为用户界面安装 VS 代码服务器
通过这些步骤,我们得到一个包含我们的 Dockerfile 描述的依赖项的容器镜像。通过将代码挂载到容器内部,代码可以在容器内部使用,但只有一份代码的副本。
在开发项目中,通常会有一份工具或先决条件列表,需要安装这些工具以准备好与项目一起工作。如果你很幸运,这个列表甚至会是最新的!通过使用dev containers,我们可以用 Dockerfile 中的一系列步骤替换文档中的工具列表来执行这些步骤。由于这些镜像可以重新构建,安装工具的标准方式现在变成了 Dockerfile。由于这是源代码控制的一部分,所以这些所需工具的更改将与其他开发人员共享,他们只需从 Dockerfile 重新构建他们的 dev 容器镜像即可更新他们的工具集。
开发容器的另一个好处是依赖项安装在容器中,因此是隔离的。这使我们能够为不同项目创建具有相同工具的不同版本的容器(例如 Python 或 Java),而不会发生冲突。这种隔离还允许我们在项目之间独立更新工具的版本。
让我们来看看如何安装 Remote-Containers 扩展。
安装 Remote-Containers
要使用 Remote-Containers 扩展,您需要安装它,并且还需要在 WSL 中安装和访问 Docker。请参阅第七章,在 WSL 中使用容器,使用 WSL 安装和使用 Docker部分以了解如何配置。如果您已经安装了 Docker Desktop,请确保将其配置为使用基于 WSL 2 的引擎。WSL 2 引擎使用在 WSL 2 中运行的 Docker 守护程序,因此您的代码文件(来自 WSL 2)可以直接挂载到容器中,而无需经过 Linux 到 Windows 文件共享。这种直接挂载可以提供更好的性能,确保文件事件被正确处理,并使用相同的文件缓存(有关更多详细信息,请参阅此博文:www.docker.com/blog/docker-desktop-wsl-2-best-practices/)
)。
一旦您配置好了 Docker,下一步是安装 Remote-Containers 扩展。您可以在 Visual Studio Code 的EXTENSIONS视图中搜索Remote-Containers
来完成此操作,或者访问marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers
。
安装了扩展后,让我们看看如何创建开发容器。
创建开发容器
要将开发容器添加到项目中,我们需要创建一个包含两个文件的.devcontainer
文件夹:
-
Dockerfile
用于描述要构建和运行的容器映像 -
devcontainer.json
以添加其他配置
这些文件的组合将为我们提供一个单容器配置。Remote-Containers 还支持使用Docker Compose的多容器配置(参见code.visualstudio.com/docs/remote/create-dev-container#_using-docker-compose
),但在本章中,我们将重点关注单容器场景。
本书附带的代码包含一个示例项目,我们将使用它来探索开发容器。请确保从github.com/PacktPublishing/Windows-Subsystem-for-Linux-2-WSL-2-Tips-Tricks-and-Techniques
在 Linux 发行版中克隆代码。克隆代码后,在 Visual Studio Code 中打开chapter-10/01-web-app
文件夹(还有一个chapter-10/02-web-app-completed
文件夹,其中包含了本节中的所有步骤作为参考)。这个示例代码还没有开发容器定义,所以让我们看看如何添加它。
添加和打开开发容器定义
开发容器的第一步是创建开发容器定义,Remote-Containers 扩展在这方面为我们提供了一些帮助。在 Visual Studio Code 中打开示例项目后,从命令面板中选择Remote-Containers: Add Development Container Configuration Files…,然后您将被提示选择一个配置:
图 10.1-显示开发容器配置列表的屏幕截图
如此屏幕截图所示,我们可以从一系列预定义的开发容器配置中选择。对于示例项目,请选择.devcontainer
文件夹,并配置devcontainer.json
和Dockerfile
以使用 Python 3。添加这些文件后,您应该会看到以下提示:
图 10.2-显示重新在容器中打开提示的屏幕截图
当 Visual Studio Code 检测到您打开了一个带有开发容器定义的文件夹时,会出现此提示。点击在容器中重新打开以在开发容器中打开文件夹。如果您错过了提示,可以使用命令面板中的Remote-Containers: Reopen in Container命令来实现相同的功能。
选择重新在容器中打开文件夹后,Visual Studio Code 将重新启动并开始构建容器镜像以运行代码服务器。您将看到一个通知:
Figure 10.3 – A screenshot showing the Starting with Dev Container notification
此截图显示了开发容器正在启动的通知。如果您点击通知,将会进入TERMINAL视图中的Dev Containers窗格,显示构建和运行容器的命令和输出。当您开始自定义开发容器定义时,此窗口对于调试场景非常有用,例如当您的容器镜像无法构建时。现在我们已经在开发容器中打开了项目,让我们开始探索它吧。
在开发容器中工作
一旦开发容器构建和启动完成,您将在devcontainer.json
文件的name
属性中看到示例代码的内容:
{
"name": "chapter-10-01-web-app",
...
在devcontainer.json
的这个片段中,开发容器的名称已更改为chapter-10-01-web-app
。此更改将在下次构建和加载开发容器时生效。如果您有时同时加载多个开发容器,将名称设置得有意义尤为有帮助,因为它会显示在窗口标题中。
接下来,让我们打开包含示例应用程序代码的app.py
文件:
Figure 10.4 – A screenshot showing an import error in app.py
在此截图中,您可以看到导入 Flask 包的行下面的红色下划线,这在 Python 扩展加载和处理文件后显示。此错误表示 Python 无法找到 Flask 包。希望这是有意义的-所有的工具都在一个只安装了 Python 的容器中运行,没有其他东西。让我们快速修复这个问题。使用Ctrl + **(反引号)打开集成终端,或者使用
pip3 install -r requirements.txt安装
requirements.txt`中列出的要求(包括 Flask)。安装了要求后,Python 语言服务器最终会更新以删除红色下划线警告。
在本章后面,我们将介绍如何在构建容器时自动安装所需的内容,以提供更流畅的体验;但是现在我们已经准备好了,让我们运行代码吧。
运行代码
示例代码包括一个描述如何启动我们的代码的.vscode/launch.json
文件。该文件允许我们配置传递给进程的命令行参数和应设置的环境变量等内容。有关launch.json
的介绍和从头开始创建它的内容,请参见第九章,Visual Studio Code 和 WSL,调试我们的应用程序部分。
通过launch.json
,我们只需按下F5即可在调试器下启动我们的应用程序。如果您想看到交互式调试器的效果,请使用F9设置断点(get_os_info
函数中的return
语句是一个好的位置)。
启动后,您将在TERMINAL视图中看到调试器命令的执行和相应的输出:
* Serving Flask app "app.py"
* Environment: development
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
在这个输出中,你可以看到应用程序启动并显示了它正在监听的地址和端口(http://127.0.0.1:5000
)。当你用鼠标悬停在这个地址上时,你会看到一个弹出窗口,显示你可以使用Ctrl + 单击来打开链接。这样做将会在你的默认 Windows 浏览器中打开该地址,并且如果你设置了断点,你会发现代码已经在那个点暂停,以便你检查变量等。一旦你完成了对调试器的探索,按下F5继续执行,你将在浏览器中看到渲染后的响应:
Figure 10.5 – 一个截图显示了 Python 应用在 Windows 浏览器中的网页
这个截图显示了浏览器加载了我们的 Python 应用的网页。请注意主机名(在截图中为831c04e3574c
,但是你会看到一个不同的 ID,因为每个容器都会改变),这是短容器 ID,它被设置为容器实例中运行应用程序的主机名。我们能够从 Windows 加载网页,是因为 Remote-Containers 扩展自动为我们设置了端口转发。这个端口转发在 Windows 上监听端口5000
,并将流量转发到我们的 Python 应用程序所在的容器中的端口5000
,以进行监听和响应。
此时,我们在 WSL 中的 Docker 中运行了一个容器,其中包含了我们的所有开发工具(包括 Python 和 Visual Studio Code 服务器),我们能够以我们期望的丰富、交互式的方式与代码一起工作。我们可以轻松地在调试器中启动代码,逐步执行代码并检查变量,然后从 Windows 与我们的 Web 应用程序进行交互。所有这些都像在主机上运行代码一样顺利,但我们拥有开发容器带来的隔离和自动化开发环境的所有优势。
接下来,我们将探索如何自定义开发容器定义,同时将我们的应用程序作为容器在开发容器中打包和运行。
在开发容器中使用容器化应用程序
到目前为止,我们已经看到了如何使用开发容器来开发应用程序,但是如果我们想要开发一个将自身打包并在容器中运行的应用程序,可能是在 Kubernetes 中呢?在本节中,我们将专注于这种情况,看看如何从开发容器内部构建和运行我们应用程序的容器镜像。
我们将再次使用本书的附带代码作为本节的起点。确保你在 Linux 发行版中从github.com/PacktPublishing/Windows-Subsystem-for-Linux-2-WSL-2-Tips-Tricks-and-Techniques
克隆代码。代码克隆完成后,用 Visual Studio Code 打开chapter-10/03-web-app-kind
文件夹(还有一个chapter-10/04-web-app-kind-completed
文件夹,其中包含了本节中所有步骤的参考)。03-web-app-kind
文件夹包含一个与我们刚刚使用的 Web 应用程序非常相似的 Web 应用程序,但是添加了一些额外的文件,以帮助我们在本章后面将应用程序集成到 Kubernetes 中。
为了能够在 Docker 中使用该应用程序,我们需要经历一些类似于我们在第七章中所经历的步骤,即在 WSL 中使用容器的构建和运行 Web 应用程序部分,只是这一次,我们将在我们的开发容器中进行操作:
-
在开发容器中设置 Docker。
-
构建应用程序 Docker 镜像。
-
运行应用程序容器。
让我们首先看看如何设置开发容器,以允许我们构建应用程序容器镜像。
在开发容器中设置 Docker
启用构建 Docker 镜像的第一步是在 Visual Studio Code 中安装docker
.devcontainer/Dockerfile
并添加以下内容:
RUN apt-get update \
&& export
DEBIAN_FRONTEND=noninteractive \"
# Install docker
&& apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common lsb-release \
&& curl -fsSL https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/gpg | apt-key add - 2>/dev/null \
&& add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]') $(lsb_release -cs) stable" \
&& apt-get update \
&& apt-get install -y docker-ce-cli \
# Install docker (END)
# Install icu-devtools
&& apt-get install -y icu-devtools \
# Clean up
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
在此代码片段中,请注意# Install docker
和# Install docker (END)
之间的行。这些行已添加以遵循 Docker 文档中的步骤,以添加apt
存储库,然后使用该存储库来apt-get install
docker-ce-cli
软件包。此时,重新构建和打开开发容器将为您提供一个带有docker
CLI 的环境,但没有守护程序与其通信。
我们已经在主机上设置了 Docker,并且 Visual Studio Code 使用此提供的 Docker 守护程序来构建和运行我们用于开发的开发容器。要在容器内构建和运行 Docker 镜像,您可以考虑在开发容器内安装 Docker。这是可能的,但可能会变得非常复杂并且会增加性能问题。相反,我们将在开发容器内重用主机上的 Docker 守护程序。在 Linux 上,默认与 Docker 的通信是通过/var/run/docker.sock
套接字进行的。使用docker
CLI 运行容器时,可以使用--mounts
开关挂载套接字(docs.docker.com/storage/bind-mounts/
)。对于开发容器,我们可以在.devcontainer/devcontainer.json
中使用mounts
属性指定此内容:
"mounts": [
// mount the host docker socket (for Kind and docker builds)
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
],
此代码段显示了devcontainer.json
中的mounts
属性,该属性指定 Visual Studio Code 在运行我们的开发容器时要使用的挂载点。此属性是一个挂载字符串的数组,在这里我们指定了我们想要一个bind
挂载(即从主机挂载),将主机上的/var/run/docker.sock
挂载到开发容器内的相同值。这样做的效果是使主机上的 Docker 守护程序的套接字在开发容器内可用。
此时,在终端中已经安装了docker
CLI 供您使用。您运行的任何docker
命令都将针对 Docker Desktop 守护程序执行;因此,例如运行docker ps
以列出容器将包括开发容器在其输出中:
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6471387cf184 vsc-03-web-app-kind-44349e1930d9193efc2813 97a394662f "/bin/sh -c 'echo Co…" 54 seconds ago Up 53 seconds
在开发容器中终端中执行的docker ps
命令的输出包括开发容器本身,确认 Docker 命令正在连接到主机 Docker 守护程序。
提示
如果您在更新 Dockerfile 和devcontainer.json
之前已经打开了开发容器(或者在修改这些文件的任何时间),您可以运行Remote-Containers: Rebuild and reopen in Container命令。此命令将重新运行开发容器的构建过程,然后重新打开它,将您对开发容器的更改应用到其中。
现在我们已经安装和配置了 Docker,让我们来构建我们应用程序的容器镜像。
构建应用程序的 Docker 镜像
要构建我们应用程序的 Docker 镜像,我们可以运行docker build
命令。由于 Docker CLI 配置为与主机 Docker 守护程序通信,我们从开发容器内构建的任何镜像实际上都是在主机上构建的。这消除了您可能期望从开发容器中获得的一些隔离性,但我们可以通过确保我们使用的镜像名称是唯一的来解决这个问题,以避免与其他项目发生名称冲突。
示例代码的根文件夹中已经有一个 Dockerfile,我们将使用它来构建应用程序的 Docker 镜像(不要与.devcontainer/Dockerfile
混淆,该文件用于构建开发容器)。Dockerfile 在python
基础镜像上构建,然后复制我们的源代码并配置启动命令。有关 Dockerfile 的更多详细信息,请参考第七章,在 WSL 中使用容器,介绍 Dockerfiles部分。
要构建应用程序镜像,请像在本章前面所做的那样打开集成终端,并运行以下命令来构建容器镜像:
docker build -t simple-python-app-2:v1 -f Dockerfile .
此命令将拉取 Python 镜像(如果不存在),并在输出Successfully tagged simple-python-app-2:v1
之前运行 Dockerfile 中的每个步骤。
现在我们已经构建了应用程序镜像,让我们运行它。
运行应用程序容器
要运行我们的镜像,我们将使用docker run
命令。从 Visual Studio Code 的集成终端中运行以下命令:
# docker run -d --network=container:$HOSTNAME --name chapter-10-example simple-python-app-2:v1
ffb7a38fc8e9f86a8dd50ed197ac1a202ea7347773921de6a34b93cec 54a1d95
在此输出中,您可以看到我们正在运行一个名为chapter-10-example
的容器,使用我们之前构建的simple-python-app-2:v1
镜像。我们指定了--network=container:$HOSTNAME
,这将新创建的容器放在与开发容器相同的 Docker 网络中。请注意,我们使用$HOSTNAME
来指定开发容器的 ID,因为容器 ID 用作运行容器中的机器名称(正如我们在第七章**中看到的,在 WSL 中使用容器的构建和运行 Docker部分)。有关--network
开关的更多信息,请参阅docs.docker.com/engine/reference/run/#network-settings
。我们可以通过从集成终端运行curl
来确认我们能够访问运行容器中的 Web 应用程序:
# curl localhost:5000
<html><body><h1>Hello from Linux (4.19.104-microsoft-standard) on ffb7a38fc8e9</h1></body></html>
在此输出中,您可以看到 Web 应用程序对curl
命令的 HTML 响应。这证实了我们可以从开发容器内部访问该应用程序。
如果您尝试从 Windows 浏览器访问 Web 应用程序,它将无法连接。这是因为 Web 应用程序的容器端口已映射到开发容器的 Docker 网络中。幸运的是,Remote-Containers 提供了一个5000
,我们可以使 Windows 中的 Web 浏览器也能访问运行在容器中的 Web 应用程序。
对于您希望以这种方式在主机上定期访问的开发容器端口,更新devcontainer.json
非常方便:
"forwardPorts": [
5000
]
在这个片段中,您可以看到forwardPorts
属性。这是一个端口数组,您可以配置它们在运行开发容器时自动转发,以节省每次手动转发的步骤。
注意
作为使用--network
开关运行 Web 应用程序容器的替代方法,我们可以配置开发容器使用主机网络(使用--network=host
,如下一节所示)。使用这种方法,开发容器重用与主机相同的网络堆栈,因此我们可以使用以下命令运行 Web 应用程序容器:
docker run -d -p 5000:5000 --name chapter-10-example simple-python-app-2:v1
在此命令中,我们使用了-p 5000:5000
来将 Web 应用程序端口 5000 暴露给主机,正如我们在第七章**中看到的,在 WSL 中使用容器的构建和运行 Docker部分。
到目前为止,我们已经设置好了开发容器,使其连接到我们主机上的 Docker,并重用它来使用我们在开发容器中安装的 Docker CLI 进行构建和运行镜像。现在我们已经测试了为我们的 Web 应用程序构建容器镜像,并检查了它是否正确运行,让我们看看在从开发容器中工作时如何在 Kubernetes 中运行它。
在开发容器中使用 Kubernetes
现在我们有了一个可以从开发容器内部构建的 Web 应用程序的容器镜像,我们将看一下运行应用程序所需的步骤,以便能够在 Kubernetes 中运行我们的应用程序。这一部分相当高级(特别是如果您对 Kubernetes 不熟悉),所以可以跳到与开发容器一起工作的提示部分,稍后再回来阅读。
让我们首先看看如何设置用于与 Kubernetes 一起工作的开发容器。
Kubernetes 与开发容器的选项
在 WSL 中使用 Kubernetes 的选项有很多。常见的选项在第七章中的在 WSL 中设置 Kubernetes部分中进行了概述。在该章节中,我们使用了 Docker 桌面中的 Kubernetes 集成,这是一种低摩擦的设置 Kubernetes 的方式。这种方法也可以用于开发容器,只需完成几个步骤(假设您已启用了 Docker 桌面集成):
-
挂载一个卷,将 WSL 中的
~/.kube
文件夹映射到开发容器中的/root/.kube
,以共享连接到 Kubernetes API 的配置。 -
在开发容器的 Dockerfile 中作为一步安装
kubectl
CLI 以便与 Kubernetes 一起使用。
第一步使用devcontainer.json
中的挂载,就像我们在前一节中看到的一样(引用用户主文件夹的标准做法是使用环境变量 - 例如${env:HOME}${env:USERPROFILE}/.kube
)。我们将在稍后介绍安装kubectl
的第二步。在本章中,我们将探索一种不同的 Kubernetes 方法,但是在附带书籍的代码中有一个chapter10/05-web-app-desktop-k8s
文件夹,其中包含已完成这两个步骤的开发容器。
虽然 Docker 桌面的 Kubernetes 集成很方便,但它增加了对主机配置的额外要求。默认情况下,开发容器只需要您安装了带有 Remote-Containers 的 Visual Studio Code 和正在运行的 Docker 守护程序,并且通过开发容器的内容满足了其余的项目要求。在 Docker 桌面中需要 Kubernetes 集成会稍微降低开发容器的可移植性。另一个考虑因素是使用 Docker 桌面集成意味着您正在使用在整个计算机上共享的 Kubernetes 集群。当您的项目涉及创建 Kubernetes 集成(如运算符或其他可能应用策略的组件)时,这种隔离的丧失可能特别重要。kind
项目(kind.sigs.k8s.io/
)提供了一种替代方法,允许我们使用 Docker 在开发容器内轻松创建和管理 Kubernetes 集群(实际上,kind 代表 Kubernetes in Docker)。如果您计划在开发容器中重用 kind,则这种方法也很有效。
在开发容器中设置 kind
在本节中,我们将逐步介绍在开发容器中安装kind
(和kubectl
)的步骤。这将允许我们使用kind
CLI 在开发容器内创建 Kubernetes 集群,然后使用kubectl
访问它们。为此,我们需要执行以下操作:
-
在 dev 容器的 Dockerfile 中添加安装 kind 和 kubectl 的步骤。
-
更新
devcontainer.json
以启用连接到 kind 集群。
要安装kind
,打开.devcontainer/Dockerfile
并添加以下RUN
命令(在以apt-get update
开头的RUN
命令之后)。
# Install Kind
RUN curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.8.1/kind-linux-amd64 && \
chmod +x ./kind && \
mv ./kind /usr/local/bin/kind
此片段中的RUN
命令遵循安装 kind 的文档(kind.sigs.k8s.io/docs/user/quick-start/#installation
),并使用curl
下载 kind 的发布二进制文件。
在上一个命令之后添加以下RUN
命令以安装kubectl
:
# Install kubectl
RUN curl -sSL -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v1.19.0/bin/linux/amd64/kubectl \
&& chmod +x /usr/local/bin/kubectl
这个RUN
步骤根据文档(kubernetes.io/docs/tasks/tools/install-kubectl/
)安装kubectl
。这些命令中的第一个使用curl
下载发布二进制文件(在本例中为版本1.19.0
)。第二个命令使下载的二进制文件可执行。
现在我们已经配置好了kind
和kubectl
的安装,我们需要对.devcontainer/devcontainer.json
进行一些更改。首先是在开发容器中添加一个.kube
文件夹的卷:
"mounts": [
// mount a volume for kube config
"source=04-web-app-kind-completed-kube,target=/root/.kube,type=volume",
// mount the host docker socket (for Kind and docker builds)
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
],
这个片段显示了我们之前使用的mounts
属性,用于将主机的 Docker 套接字与新配置的挂载绑定,以创建一个以/root/.kube
文件夹为目标的卷。当我们运行kind
创建一个 Kubernetes 集群时,它将把与集群通信的配置保存在这个文件夹中。通过添加一个卷,我们确保该文件夹的内容在开发容器的实例(和重建)之间持久存在,以便我们仍然可以连接到 Kubernetes 集群。
如前所述,kind
将 Kubernetes API 端点列为127.0.0.1
(本地 IP 地址)。这指的是主机,但是开发容器默认情况下位于一个隔离的 Docker 网络中。为了使开发容器能够使用kind
生成的配置访问 Kubernetes API,我们可以通过更新.devcontainer/devcontainer.json
将开发容器放入主机网络模式中:
"runArgs": [
// use host networking (to allow connecting to Kind clusters)
"--network=host"
],
在这个片段中,您可以看到runArgs
属性。这允许我们配置附加参数,当 Remote-Containers 启动我们的开发容器时,它会将这些参数传递给docker run
命令。在这里,我们设置了--network=host
选项,它将在与主机相同的网络空间中运行容器(有关更多详细信息,请参见docs.docker.com/engine/reference/run/#network-settings
)。
通过这些更改,我们可以重新构建和重新打开开发容器,然后准备创建一个 Kubernetes 集群并在其中运行我们的应用程序!
使用 kind 在 Kubernetes 集群中运行我们的应用程序
现在,我们已经准备好从开发容器内部创建一个 Kubernetes 集群了。要创建一个集群,我们将使用集成终端中的kind
CLI:
图 10.6 - 显示 kind 集群创建的屏幕截图
在这里,您可以看到运行kind create cluster --name chapter-10-03
的输出。如果节点上没有容器镜像,kind
CLI 会负责拉取容器镜像,然后在设置集群的步骤中更新输出。默认情况下,kind
创建一个单节点集群,但是有一系列的配置选项,包括设置多节点集群(参见kind.sigs.k8s.io/docs/user/configuration/
)。
现在,我们可以使用这个集群来运行我们的应用程序(假设您已经在前一节中构建了容器镜像;如果没有,请运行docker build -t simple-python-app-2:v1 -f Dockerfile.
)。
为了使我们的应用程序的容器镜像在kind
集群中可用,我们需要运行kind load
(参见kind.sigs.k8s.io/docs/user/quick-start/#loading-an-image-into-your-cluster
):
# kind load docker-image --name chapter-10-03 simple-python-app-2:v1
Image: "simple-python-app-2:v1" with ID "sha256:7c085e8bde177aa0abd02c36da2cdc68238e672f49f0c9b888581b 9602e6e093" not yet present on node "chapter-10-03-control-plane", loading...
在这里,我们使用kind load
命令将simple-python-app-2:v1
镜像加载到我们创建的chapter-10-03
集群中。这将在集群中的所有节点上加载镜像,以便我们在 Kubernetes 中创建部署时可以使用它。
示例应用程序中的manifests
文件夹包含了在 Kubernetes 中配置应用程序的定义。请参考第七章,在 WSL 中使用容器,在 Kubernetes 中运行 Web 应用程序部分,其中有一个非常相似的应用程序的部署文件的演示和解释。我们可以使用kubectl
将应用程序部署到 Kubernetes 中:
# kubectl apply -f manifests/
deployment.apps/chapter-10-example created
service/chapter-10-example created
在这里,我们使用kubectl apply
命令和-f
开关来传递要加载清单的路径。在这种情况下,我们指定manifests
文件夹,以便kubectl
将应用于文件夹中的所有文件。
我们的 Web 应用现在在kind
集群中的一个节点上运行,并且我们刚刚应用的配置创建了一个 Kubernetes 服务来公开端口5000
。这个服务只在kind
集群内部可用,所以我们需要运行kubectl port-forward
来将本地端口转发到该服务:
# kubectl port-forward service/chapter-10-example 5000
Forwarding from 127.0.0.1:5000 -> 5000
Forwarding from [::1]:5000 -> 5000
在输出中,您可以看到kubectl port-forward
命令用于指定service/chapter-10-03-example
服务作为目标,并将5000
作为我们要转发的端口。这将设置从开发容器中的本地端口5000
到在kind
中运行的应用的服务的端口5000
的端口转发。
如果您创建一个新的集成终端(通过点击集成终端右上角的加号符号),您可以使用它来运行curl
命令来验证服务是否正在运行:
# curl localhost:5000
<html><body><h1>Hello from Linux (4.19.104-microsoft-standard) on chapter-10-example-99c88ff47-k7599</h1></body></html>
这个输出显示了从开发容器内部运行的curl localhost:5000
命令,并使用kubectl
端口转发访问在kind
集群中部署的 Web 应用。
当我们在本章早些时候使用 Docker 处理应用程序时,我们在devcontainer.json
中配置了forwardPorts
属性来转发端口5000
。这意味着 Visual Studio Code 已经设置好了将 Windows 上的端口5000
转发到开发容器中的端口5000
。任何发送到开发容器中端口5000
的流量都将由我们刚刚运行的kubectl
端口转发命令处理,并转发到 Kubernetes 服务上的端口5000
。这意味着我们可以在 Windows 的浏览器中打开http://localhost:5000
:
图 10.7 - Windows 浏览器显示 Kubernetes 中的应用的截图
在这个截图中,我们可以看到 Windows 浏览器通过http://localhost:5000
访问我们在 Kubernetes 中的应用。这是因为 Visual Studio Code 将 Windows 端口5000
转发到开发容器内部的端口5000
,这由kubectl port-forward
处理,并转发到我们为应用部署的 Kubernetes 服务。
在本节中,我们使用了Visual Studio Code,Remote-Containers和Docker来创建一个容器化的开发环境,用于处理 Web 应用。我们看到了如何使用它来构建和运行我们的 Web 应用的容器镜像,然后创建一个 Kubernetes 集群,并在集群中部署和测试我们的应用,包括如何从主机 Windows 机器上的浏览器访问在 Kubernetes 中运行的 Web 应用。我们实现了所有这些,而不需要向主机机器添加任何其他要求,使得这个可移植的解决方案对于任何拥有 Visual Studio Code 和 Docker 的人来说都是快速上手的。
在本章的最后一节中,我们将介绍一些与开发容器一起工作的生产力技巧。
与开发容器一起工作的提示
在本节中,我们将介绍一些可以用来优化与开发容器一起工作体验的技巧。让我们从在构建完成后自动化开发容器内部的步骤开始。
postCreateCommand 和自动化 pip 安装
在本章的早些示例中,我们在构建开发容器后需要运行pip install
,并且每次在更改其配置后重新构建开发容器时都需要运行此命令。为了避免这种情况,可能会诱惑将RUN
步骤添加到开发容器的 Dockerfile 中以执行pip install
,但我更倾向于不将应用程序包放入开发容器镜像中。应用程序包依赖关系往往会随着时间的推移而发展,并且将它们构建到镜像中(并重新构建镜像以进行安装)会感觉有点笨重。随着时间的推移,在使用开发容器时,我的经验法则是在开发容器镜像中安装工具,并在运行时在开发容器内安装应用程序包。幸运的是,开发容器为我们提供了在devcontainer.json
中配置postCreateCommand
选项的功能:
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pip3 install -r requirements.txt",
这个片段显示了将postCreateCommand
配置为运行pip install
步骤。在重新构建镜像后,Visual Studio Code 将在启动开发容器时自动运行postCreateCommand
。
如果要运行多个命令,可以将它们组合为command1 && command2
,或将它们放在一个脚本文件中,并从postCreateCommand
运行该脚本。
当我们查看自动化开发容器任务的设置时,让我们再次关注端口转发。
端口转发
在本章的早些时候,我们利用了 Visual Studio Code 中的端口转发功能,将选定的流量从 Windows 主机转发到开发容器中,例如允许 Windows 浏览器连接到运行在开发容器中的 Web 应用程序。设置端口转发的一种方法是使用devcontainer.json
文件:
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
5000,
5001
]
在这个片段中,我们在forwardPorts
属性中指定了端口5000
和5001
。当 Visual Studio Code 启动开发容器时,它将自动开始转发这些端口,帮助我们平滑地进行工作流程。
要查看正在转发的端口,请切换到REMOTE EXPLORER视图(例如,通过运行Remote Explorer: Focus on Forwarded Ports View命令):
图 10.8 - 显示转发端口视图的屏幕截图
在这个屏幕截图中,您可以看到当前配置的转发端口列表。将鼠标悬停在端口上将显示屏幕截图中看到的地球和叉图标。单击地球将在默认的 Windows 浏览器中打开该端口,单击叉将停止共享该端口。
forwardPorts
配置提高了生产力。
接下来,我们将重新讨论卷挂载的主题,并查看一些更多的示例。
挂载卷和 Bash 历史记录
在本章中,我们已经看到了几个配置挂载的示例,它们分为两个不同的类别:
-
将主机中的文件夹或文件挂载到容器中
-
将卷挂载到容器中以在容器实例之间保留数据
这两个类别中的第一个是将主机卷挂载到容器中,这是我们用来将主机 Docker 套接字(/var/run/docker.sock
)挂载到开发容器中的方法。这也可以用于挂载诸如~/.azure
之类的文件夹,从主机中将 Azure CLI 身份验证数据带入开发容器中,以避免在开发容器内再次登录。
第二类挂载创建了一个 Docker 卷,每次运行开发容器时都会挂载该卷。这在开发容器内提供了一个文件夹,其内容在容器重新构建时得以保留。这在处理包缓存文件时非常有用,如果您有大文件,您可以避免重复下载。另一个非常有用的例子是在开发容器中保留 Bash 历史记录。为此,我们可以在 Dockerfile 中配置bash history
的位置:
# Set up bash history
RUN echo "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" >> /root/.bashrc
此代码片段将配置添加到.bashrc
文件(在 Bash 启动时运行),以配置.bash_history
文件的位置为/commandhistory
文件夹。单独使用它并没有太大作用,但如果将/commandhistory
文件夹设置为挂载卷,结果就是在开发容器的实例之间保留 Bash 历史记录。实际上,这个配置还有一个额外的好处。如果没有开发容器,所有项目在主机上共享相同的 Bash 历史记录,因此如果您在几天内不使用某个项目,可能意味着与该项目相关的命令已从您的历史记录中删除。使用开发容器的这个配置,Bash 历史记录是特定于容器的,因此加载开发容器会恢复您的 Bash 历史记录,而不管您在主机上同时运行了哪些命令(确保为卷指定一个特定于项目的名称)。
这是一个说明所讨论的示例的配置:
"mounts": [
// mount the host docker socket
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
// mount the .azure folder
"source=${env:HOME}${env:USERPROFILE}/.azure,target=//root/.azure,type=bind",
// mount a volume for bash history
"source=myproject-bashhistory,target=/commandhistory,type=volume",
],
此代码片段显示了我们在本节中讨论的各种挂载方式:
-
将主机的
/var/run/docker.sock
挂载到开发容器中以公开主机 Docker 套接字。 -
将主机的
.azure
文件夹挂载到开发容器中,以将缓存的 Azure CLI 身份验证带入开发容器。请注意,使用环境变量替换来定位源中的用户文件夹。 -
挂载卷以在开发容器实例之间保留 Bash 历史记录。
挂载卷是在使用开发容器时非常有用的工具,它可以通过允许我们将主机文件夹带入开发容器来大大提高生产力,以重用 Azure CLI 身份验证。它还可以在开发容器实例之间提供持久的文件存储,例如保留 Bash 历史记录或启用软件包缓存。
我们将看一下确保构建开发容器镜像的可重复性的最后一个提示。
使用固定版本的工具
在配置开发容器时,很容易(也很诱人)使用安装最新版本工具的命令。运行Remote-Containers: Add Development Container Configuration Files…命令时使用的起始开发容器定义通常使用安装最新版本工具的命令,而且很多工具的安装文档都指导您使用相同的命令。
如果您的开发容器 Dockerfile 中的命令安装最新版本的工具,那么您团队中的不同成员在其开发容器中可能会有不同版本的工具,这取决于他们构建开发容器的时间以及那时工具的最新版本是什么。此外,您可能会添加一个新工具并重新构建开发容器,并获取其他工具的更新版本。通常,工具在版本之间保持合理的兼容性水平,但偶尔会在版本之间更改其行为。这可能导致奇怪的情况,其中开发容器工具对一个开发人员有效,但对另一个开发人员无效,或者工具在重新构建开发容器(例如,添加新工具)之前工作正常,但然后无意中获取了其他工具的新版本。这可能会干扰您的工作流程,我通常更喜欢将工具固定到特定版本(例如本章中的kind
和kubectl
),然后在方便的时间或需要时明确更新它们的版本。
始终安装的扩展和 dotfiles
在设置开发容器时,您可以指定在创建开发容器时要安装的扩展。为此,您可以将以下内容添加到devcontainer.json
中:
"extensions": [
"redhat.vscode-yaml",
"ms-vsliveshare.vsliveshare"
],
在这里,您可以在 JSON 中看到extensions
属性,它指定了一个扩展 ID 的数组。要找到扩展的 ID,请在 Visual Studio Code 的EXTENSIONS视图中搜索扩展并打开它。您将看到以下详细信息:
图 10.9 - 显示 Visual Studio Code 中扩展信息的屏幕截图
在此屏幕截图中,您可以看到扩展 ID(ms-vsliveshare.vsliveshare
)的信息被突出显示。通过在此处添加扩展,您可以确保任何使用开发容器的人都会安装相关的扩展。
Remote-Containers 扩展还具有一个名为Always Installed Extensions(或Default Extensions)的功能。此功能允许您配置一个在开发容器中始终要安装的扩展列表。要启用此功能,请选择Preferences: Open user settings (JSON)命令面板中的选项以打开设置 JSON 文件,并添加以下内容:
"remote.containers.defaultExtensions": [
"mhutchie.git-graph",
"trentrand.git-rebase-shortcuts"
],
在设置文件的这个片段中,您可以看到remote.containers.defaultExtensions
属性。这是一个扩展 ID 数组,就像devcontainer.json
中的extensions
属性一样,但是在此处列出的扩展将始终安装在您在计算机上构建的开发容器中。
Remote-Containers 扩展支持的一个相关功能是.bash_rc
和.gitconfig
。要了解有关 dotfiles 的更多信息,请访问dotfiles.github.io/
。
Remote-Containers 中的 dotfile 支持允许您指定包含 dotfiles 的 Git 存储库的 URL,它们应该在开发容器中克隆到的位置以及克隆存储库后要运行的命令。这些可以在设置 JSON 中配置:
"remote.containers.dotfiles.repository": "stuartleeks/dotfiles",
"remote.containers.dotfiles.targetPath": "~/dotfiles",
"remote.containers.dotfiles.installCommand": "~/dotfiles/install.sh",
在这里,我们可以看到与我们刚刚描述的设置相对应的三个 JSON 属性。请注意,remote.containers.dotfiles.repository
的值可以是完整的 URL,例如github.com/stuartleeks/dotfiles.git
,也可以是stuartleeks/dotfiles
。
我喜欢使用 dotfiles 功能来设置 Bash 别名。我在计算机上的早期时间大部分都是在 MS-DOS 上度过的,我仍然发现我更容易输入cls
和md
这样的命令,而不是它们的等效命令clear
和mkdir
。使用 dotfiles 进行此配置有助于提高我在开发容器中的生产力,但是其他开发容器用户可能不需要或不想要这个配置。
有了 dotfiles 和Always Installed Extensions功能,现在需要做出一个决定:配置和扩展应该在开发容器定义中设置,还是使用 dotfiles 和Always Installed Extensions?为了回答这个问题,我们可以问自己扩展或设置是否是开发容器功能的核心部分或个人偏好。如果答案是个人偏好,那么我会将其放在 dotfiles 或Always Installed Extensions中。对于与开发容器的目的直接相关的功能,我会将其包含在开发容器定义中。
例如,如果我正在使用用于 Python 开发的开发容器,那么我会在开发容器定义中包含 Python 扩展。同样,对于使用 Kubernetes 的项目,我会在开发容器的 Dockerfile 中包含kubectl
并为其配置 Bash 完成。我还会包含 RedHat YAML 扩展,以获得 Kubernetes YAML 文件的完成帮助(请参阅marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml
)。
无论是 dotfiles 还是Always Installed Extensions都可以是确保您的环境和开发容器体验熟悉和高效的好方法。
本节介绍了一些有助于提高开发容器生产力的提示,例如在重新构建开发容器后自动运行命令以及在开发容器启动时自动转发端口。
要了解有关配置开发容器的选项的更多信息,请参阅code.visualstudio.com/docs/remote/containers
。
概述
在本章中,您已经看到了 Visual Studio Code Remote-Containers 扩展如何允许我们使用标准的 Dockerfile 来定义一个容器来进行开发工作,同时保持 Visual Studio Code 的丰富交互环境。这些开发容器允许我们构建隔离的开发环境,以打包特定于项目的工具和依赖项,消除了通常在团队中同时协调工具更新的需要。此外,通过将开发容器定义包含在源代码控制中,团队成员可以轻松创建(和更新)开发环境。在处理 Web 应用程序时,您了解了如何将端口转发到在容器中运行的应用程序,以便您可以在 Windows 浏览器中浏览 Web 应用程序,同时在容器中进行交互式调试。
您还看到了如何通过共享主机 Docker 守护程序在开发容器中构建和使用容器化应用程序。本章考虑了从开发容器中使用 Kubernetes 的不同选项,并且您了解了如何在开发容器中配置kind
以在主机机器上满足最低要求的 Kubernetes 环境。
最后,本章提供了一些有关使用开发容器的技巧。您了解了如何在创建开发容器后自动化步骤,以及如何在开发容器启动时自动转发端口。您还了解了如何从主机挂载文件夹或文件,以及如何创建持久化文件的卷,跨开发容器实例保留文件(例如,保留 Bash 历史记录或其他生成的数据)。所有这些方法都提供了使用开发容器简化开发流程的方式,帮助您专注于想要编写的代码。
使用 Remote-Containers 可能需要一些额外的思考来设置项目的开发环境,但它为个人和团队提供了一些引人注目的优势,包括隔离和可重复使用的开发环境。
在下一章中,我们将返回 WSL,并查看在 WSL 中使用命令行工具的各种技巧。
第十一章:命令行工具的生产力技巧
在本章中,我们将涵盖一些使用几种不同常见命令行工具的技巧。我们将首先看看如何提高在 WSL 中使用 Git 的生产力和改善体验。Git 被广泛使用,通过提高使用它进行源代码控制的任何项目的生产力,都会得到改善。之后,我们将看看 Azure 的az
和 Kubernetes 的kubectl
。对于这两个 CLI,我们将部署一个简单的示例资源,然后展示一些使用它们查询数据的技巧。与许多 CLI 一样,az
和kubectl
都提供了在az
或kubectl
中获取数据的选项,这些部分涵盖的技术可能与您正在使用的其他 CLI 相关。通过有效地学习如何操作 JSON,您可以打开使用各种 API 和 CLI 进行脚本编写和自动化的新可能性。
在本章中,我们将涵盖以下主要主题:
-
使用 Git
-
使用 JSON
-
使用 Azure CLI(
az
) -
使用 Kubernetes CLI(
kubectl
)
让我们开始探索一些使用 Git 的技巧。
使用 Git
毫无疑问,Git 是一个常用的源代码控制系统。最初由 Linus Torvalds 编写用于 Linux 内核源代码,现在被广泛使用,包括微软等公司,它被广泛使用,包括用于 Windows 开发(有关更多信息,请参阅docs.microsoft.com/en-us/azure/devops/learn/devops-at-microsoft/use-git-microsoft
)。
在本节中,我们将看一些在 WSL 中使用 Git 的技巧。一些技巧在之前的章节中已经涵盖,并提供了进一步的信息链接,而另一些是新技巧 - 这两者在这里都联系在一起,方便参考。
让我们从大多数命令行工具的快速胜利开始:bash 自动补全。
Git 的 Bash 自动补全
在使用许多命令行工具时,bash 自动补全可以节省大量的输入时间,git
也不例外。
例如,git com<TAB>
将产生git commit
,而git chec<TAB>
将产生git checkout
。如果你输入的部分命令不足以指定单个命令,那么 bash 自动补全似乎不会做任何事情,但按两次Tab将显示选项。举个例子:
$ git co<TAB><TAB>
commit config
$ git co
在这里,我们看到git co
可以完成为git commit
或git config
。
Bash 自动补全不仅仅是完成命令名称;你可以使用git checkout my<TAB>
来完成分支名称为git checkout my-branch
。
一旦你习惯了 bash 自动补全,你会发现它可以大大提高生产力!
接下来,让我们看看与远程 Git 仓库进行身份验证的选项。
使用 Git 进行身份验证
通过Secure Shell(SSH)密钥进行 Git 身份验证是一种强大的方法。这种身份验证方法重用 SSH 密钥,通常用于与远程机器建立 SSH 连接,以通过 Git 进行身份验证,并且受到主要的 Git 源代码控制提供者的支持。在第五章,Linux 与 Windows 的互操作性,在SSH 代理转发部分,我们看到如何配置 WSL 以重用存储在 Windows 中的 SSH 密钥。如果你已经设置了这个,它还可以让你在 WSL 中使用 SSH 密钥。
或者,如果您在 Windows 和 WSL 之间进行混合开发并希望在它们之间共享 Git 身份验证,则可能希望为 WSL 配置 Windows 的 Git 凭据管理器。这也支持在 GitHub 或 Bitbucket 等提供商上使用双因素身份验证(有关更多信息,请参见github.com/Microsoft/Git-Credential-Manager-for-Windows
)。要使用此功能,您必须在 Windows 中安装 Git。要配置,请从您的distribution(distro)运行以下命令:
git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/libexec/git-core/git-credential-manager.exe"
此命令将 Git 配置为启动 Git Credential Manager for Windows 来处理与远程存储库的身份验证。通过 Windows 访问 Git 远程存储库存储的任何凭据将被 WSL 重用(反之亦然)。有关更多详细信息,请参见docs.microsoft.com/en-us/windows/wsl/tutorials/wsl-git#git-credential-manager-setup
。
认证问题解决后,让我们看一下在 Git 中查看历史的几个选项。
查看 Git 历史
在 WSL 中使用 Git 时,有许多不同的方法可以查看 Git 存储库中提交的历史记录。在这里,我们将看看以下不同的选项:
-
使用
git
CLI -
使用 Windows 的图形 Git 工具
-
使用 Visual Studio Code Remote-WSL
第一个选项是在 CLI 中使用git log
命令:
$ git log --graph --oneline --decorate --all
* 35413d8 (do-something) Add goodbye
| * 44da775 (HEAD -> main) Fix typo
| * c6d17a3 Add to hello
|/
* 85672d8 Initial commit
在git log
的输出中,您可以看到使用一些附加开关运行git log
命令产生了简洁的输出,使用文本艺术来显示分支。这种方法很方便,因为它可以直接从 WSL 的命令行中使用,并且除了 WSL 中安装的 Git 之外不需要安装任何东西。但是,这个命令可能有点繁琐,所以您可能希望创建一个 Git 别名,如下所示:
$ git config --global --replace-all alias.logtree 'log --graph --oneline --decorate --all'
在这里,我们使用git config
命令为先前的 Git 命令创建了一个名为logtree
的别名。创建后,我们现在可以运行git logtree
来生成先前的输出。
如果您在 Windows 上使用 Git 的图形工具,您可以将其指向 WSL 中的 Git 存储库。在第九章《Visual Studio Code 和 WSL》的《查看 Git 历史》部分中,我们看到了如何使用 Git 附带的gitk
实用程序。例如,我们可以从 WSL shell 中的 Git 存储库文件夹运行gitk.exe --all
来启动 Windows 的gitk.exe
可执行文件:
图 11.1 - 显示 Windows 中 gitk 实用程序的屏幕截图,显示了 WSL Git 存储库
在这个屏幕截图中,我们可以看到gitk
实用程序在 Windows 中运行,并显示了之前使用git log
看到的相同的 Git 存储库。因为我们是从 WSL 的 shell 中启动它的,它捕获了\\wsl$
共享,用于从 Windows 访问 WSL 中的 shell 当前文件夹(有关\\wsl$
共享的更多信息,请参见第四章《Windows 到 Linux 的互操作性》,《从 Windows 访问 Linux 文件》部分)。这种方法的一个潜在问题是通过\\wsl$
共享访问文件会有性能开销,对于大型 Git 存储库,这可能会使 Windows 的 Git 工具加载缓慢。
我们在第九章《Visual Studio Code 和 WSL》的《查看 Git 历史》部分中看到的另一个选项是使用 Visual Studio Code。通过使用 Remote-WSL 扩展,我们可以安装其他扩展程序,使它们实际在 WSL 中运行。Git Graph 扩展程序是 Visual Studio Code 的一个方便的补充,允许您以图形方式查看 Git 历史,并且与 Remote-WSL 配合良好。您可以在这里看到一个例子:
图 11.2 – 显示 Visual Studio Code 中的 Git Graph 扩展的屏幕截图
这个屏幕截图再次显示了相同的 Git 存储库,但这次是在 Visual Studio Code 中使用 Git Graph 扩展。由于这个扩展是通过 Remote-WSL 在 WSL 中加载的,所有对 Git 存储库的访问都是直接在 WSL 中执行的,而不会通过 \\wsl$
共享进行查询时的性能开销。
在这里我们看到了一些方法,每种方法都有其自身的好处,在各自的上下文中都很有用。Git CLI 方法在你已经在终端时很方便,并且它在 WSL 中运行,因此性能很好。对于检查复杂的分支和历史记录,图形工具往往是最好的选择。然而,正如前面提到的,从 Windows 使用图形 Git 工具会产生 \\wsl$
共享的性能开销 – 通常情况下这是不明显的,但对于文件或历史记录很多的 Git 存储库来说,它可能开始变得更加显著。在这些情况下,或者当我已经在编辑器中工作时,我发现 Visual Studio Code 的 Git Graph 等扩展非常有用,它提供了图形化的可视化,而没有性能开销。
接下来,我们将看看在使用 Git 时改进我们的 bash 提示。
bash 提示中的 Git 信息
在 Git 存储库中的文件夹中使用 bash 时,默认提示不会给出有关 Git 存储库状态的任何提示。有各种选项可以将 Git 存储库的上下文添加到 bash 中,我们将在这里看一些选项。第一个选项是 bash-git-prompt (https://github.com/magicmonty/bash-git-prompt),它在 Git 存储库中自定义了你的 bash 提示。你可以在这里看到一个例子:
图 11.3 – 显示 bash-git-prompt 的屏幕截图
正如这个屏幕截图所示,bash-git-prompt
显示了你当前所在的分支(在这个例子中是 main
)。它还指示了你的本地分支是否有提交要推送,或者是否有要从远程分支拉取的提交,通过上下箭头来表示。上箭头表示有提交要推送,下箭头表示有提交要拉取。最后,它显示了你是否有未提交的本地更改 – 在这个例子中是 +1
。
要安装 bash-git-prompt
,首先使用以下命令克隆存储库:
git clone https://github.com/magicmonty/bash-git-prompt.git ~/.bash-git-prompt --depth=1
这个 git clone
命令将存储库克隆到用户文件夹中的 .bash-git-prompt
文件夹中,并使用 --depth=1
仅拉取最新的提交。
接下来,在你的用户文件夹中的 .bashrc
中添加以下内容:
if [ -f "$HOME/.bash-git-prompt/gitprompt.sh" ]; then
GIT_PROMPT_ONLY_IN_REPO=1
source $HOME/.bash-git-prompt/gitprompt.sh
fi
这个片段将 GIT_PROMPT_ONLY_IN_REPO
变量设置为仅在带有 Git 存储库的文件夹中使用自定义提示,然后加载 git
提示。现在,重新打开你的终端并切换到一个 Git 存储库的文件夹中,看看 bash-git-prompt
的效果。有关其他配置选项,请参阅 github.com/magicmonty/bash-git-prompt
上的文档。
丰富你的 bash 提示的另一个选项是 bash-git-prompt
,它接管了你的一般提示体验,为提示添加了 Git 和 Kubernetes 等内容。在下面的屏幕截图中可以看到 Powerline 提示的一个例子:
图 11.4 – 显示 Powerline 提示的屏幕截图
正如这个屏幕截图所示,Powerline 使用了一些特殊的字体字符,并非所有字体都设置了这些字符,所以第一步是确保我们有一个合适的字体。Windows 终端附带了一个名为 CascadiaCodePL.ttf
和 CascadiaMonoPL.ttf
的字体,可以通过在 Windows Explorer 中右键单击 ttf
文件夹中的文件并选择 安装 来安装。
安装了 Powerline 字体后,我们需要配置终端来使用它。如果你正在使用 Windows 终端,那么启动它并按下 Ctrl + , 加载设置,并添加以下内容:
"profiles": {
"defaults": {
"fontFace": "Cascadia Mono PL"
},
在这里,我们将默认的fontFace
值设置为我们刚安装的Cascadia Mono PL
(Powerline)字体。要更改单个配置文件的字体,请参见第三章,开始使用 Windows 终端,更改字体部分。
现在我们已经设置了一个带有 Powerline 字体的终端,我们可以安装 Powerline。有几种变体,我们将使用来自github.com/justjanne/powerline-go/releases
的powerline-go-linux-amd64
版本,并将其保存为powerline-go
,放在 WSL 发行版的PATH
中的某个位置,例如/usr/local/bin
。(另一种选择是通过Go安装,但发行版存储库可能停留在旧版本的 Go 上,导致不兼容-如果您更喜欢尝试此选项,请参考 Windows 终端文档:docs.microsoft.com/en-us/windows/terminal/tutorials/powerline-setup
。)
安装了powerline-go
后,我们可以通过将以下内容添加到bashrc
来配置 bash 使用它:
function _update_ps1() {
PS1="$(powerline-go -error $?)"
}
if [ "$TERM" != "linux" ] && [ "$(command -v powerline-go > /dev/null 2>&1; echo $?)" == "0" ]; then
PROMPT_COMMAND="_update_ps1; $PROMPT_COMMAND"
fi
在这里,我们创建了一个_update_ps1
函数,调用了powerline-go
。这是添加额外开关以控制powerline-go
行为的地方-有关更多详细信息,请参阅文档:github.com/justjanne/powerline-go#customization
。
在使用 Git 时,调整提示以自动获取 Git 仓库的上下文可以使您更轻松地选择任何选项。将此与在 Git 中设置身份验证以在 Windows 和 WSL 之间共享,并了解在不同情况下如何最好地查看 Git 历史记录相结合,您将能够在 WSL 中高效地使用 Git。
在下一节中,我们将看一下几种处理 JSON 数据的方法。
处理 JSON
自动化复杂任务可以节省数小时的手动劳动。在本节中,我们将探讨一些处理 JSON 数据的技术,这是许多命令行工具和 API 允许您使用的常见格式。在本章后面,我们将展示一些示例,说明您可以如何使用这些技术轻松地创建和发布内容到云网站或 Kubernetes 集群。
对于本节,书籍的附带代码中有一个示例 JSON 文件。您可以使用 Git 从github.com/PacktPublishing/Windows-Subsystem-for-Linux-2-WSL-2-Tips-Tricks-and-Techniques
克隆此代码。示例 JSON 名为wsl-book.json
,位于chapter-11/02-working-with-json
文件夹中,基于一本书的章节和标题的 JSON 描述。此处显示了此 JSON 的片段:
{
"title": "WSL: Tips, Tricks and Techniques",
"parts": [
{
"name": "Part 1: Introduction, Installation and Configuration",
"chapters": [
{
"title": "Introduction to the Windows Subsystem for Linux",
"headings": [
"What is the Windows Subsystem for Linux?",
"Exploring the Differences between WSL 1 and 2"
]
},
...
"name": "Part 2: Windows and Linux - A Winning Combination",
"chapters": [
{
...
此片段显示了示例 JSON 的结构。值得花一些时间熟悉它,因为它是本节示例的基础。本节中的示例假定您在包含示例 JSON 的文件夹中打开了一个 shell。
让我们开始使用一个流行的实用程序jq
。
使用 jq
我们将首先看一下jq
,它是一个非常方便的用于处理 JSON 字符串的实用程序,并且在主要平台上都受支持。完整的安装选项列在stedolan.github.io/jq/download/
上,但您可以通过在 Debian/Ubuntu 上运行sudo apt-get install jq
来快速开始。
在其最基本的形式中,jq
可用于格式化输入。例如,我们可以将 JSON 字符串传输到jq
中:
$ echo '[1,2,"testing"]' | jq
[
1,
2,
"testing"
]
在这个命令的输出中,你可以看到jq
已经将紧凑的 JSON 输入转换成了格式良好的输出。当与返回紧凑 JSON 的 API 进行交互时,这个功能本身就很有用。然而,jq
真正的威力在于它的查询能力,我们将在本节中探讨这些能力。作为一个可以实现的预演,看一下下面的例子:
$ cat ./wsl-book.json | jq ".parts[].name"
"Part 1: Introduction, Installation and Configuration"
"Part 2: Windows and Linux - A Winning Combination"
"Part 3: Developing with Windows Subsystem for Linux"
这个输出显示了jq
提取和输出样本 JSON 中部件的name
值。当使用返回 JSON 数据的 API 和命令行工具进行脚本编写时,这种能力非常有用,我们将从一些简单的查询开始,逐渐发展到更复杂的查询。你可以使用jq
CLI 或者在jqplay.org
上使用jq playground来跟随这些例子,就像在这里的截图中看到的那样。
图 11.5 - 展示 jq playground 的截图
这个截图显示了在jq
playground 中打开的前面的例子。在左上角,你可以看到过滤器(.parts[].name
),在下面是输入的 JSON,右边是jq
的输出。当你在处理复杂的查询时,playground 可以是一个有用的环境,底部的命令行部分甚至会给出你可以复制并在脚本中使用的命令行。
现在你已经知道jq
可以做什么了,让我们从一个简单的查询开始。我们要处理的 JSON 有两个顶级属性:title
和parts
。如果我们想提取title
属性的值,我们可以使用以下查询:
$ cat ./wsl-book.json | jq ".title"
"WSL: Tips, Tricks and Techniques"
在这里,我们使用了.title
过滤器来提取title
属性的值。注意输出中的值被引号引起来,因为jq
默认输出 JSON。在脚本中将这个值赋给一个变量时,通常希望得到没有引号的值,我们可以使用jq
的-r
选项来获得原始输出:
$ BOOK_TITLE=$(cat ./wsl-book.json | jq ".title" -r)
$ echo $BOOK_TITLE
WSL: Tips, Tricks and Techniques
这个输出显示了使用-r
选项来获得原始(未引用)输出并将其赋给一个变量。
在这个例子中,我们使用了title
属性,它是一个简单的字符串值。另一个顶级属性是parts
,它是一个 JSON 对象的数组:
$ cat ./wsl-book.json | jq ".parts"
[
{
"name": "Part 1: Introduction, Installation and Configuration",
"chapters": [
{
"title": "Introduction to the Windows Subsystem for Linux",
"headings": [
"What is the Windows Subsystem for Linux?",
"Exploring the Differences between WSL 1 and 2"
]
},
...
在这个命令的输出中,我们看到检索parts
属性返回了属性的完整值。我们可以将过滤器更改为.parts[0]
来获取parts
数组中的第一个项目,然后进一步扩展过滤器,如果我们想要获取第一个部件的名称,就像这样:
$ cat ./wsl-book.json | jq ".parts[0].name"
"Part 1: Introduction, Installation and Configuration"
在这里,我们看到了如何构建一个查询来沿着 JSON 数据的层次结构进行选择属性和索引数组以选择特定的值。有时候能够获得一个数据列表是很有用的 - 例如,获取所有部件的名称。我们可以使用以下命令来实现:
$ cat ./wsl-book.json | jq ".parts[].name"
"Part 1: Introduction, Installation and Configuration"
"Part 2: Windows and Linux - A Winning Combination"
"Part 3: Developing with Windows Subsystem for Linux"
正如你在这个例子中看到的,我们省略了前一个过滤器中的数组索引,jq
已经处理了剩下的过滤器(.name
)并针对parts
数组的每个项目进行了处理。与单一值输出一样,我们可以添加-r
选项,以便在脚本中更容易地处理输出的未引用字符串。或者,如果我们正在处理 API,我们可能希望构建 JSON 输出 - 例如,要将前面的值输出为数组,我们可以将过滤器包装在方括号中:[.parts[].name]
。
到目前为止,我们只使用了一个单一的过滤表达式,但是jq
允许我们将多个过滤器链接在一起,并将一个过滤器的输出作为下一个过滤器的输入。例如,我们可以将.parts[].name
重写为.parts[] | .name
,这将产生相同的输出。从这里,我们可以将第二个过滤器改为{name}
,以产生一个带有name
属性的对象,而不仅仅是名称值:
$ cat ./wsl-book.json | jq '.parts[] | {name}'
{
"name": "Part 1: Introduction, Installation and Configuration"
}
{
"name": "Part 2: Windows and Linux - A Winning Combination"
}
{
"name": "Part 3: Developing with Windows Subsystem for Linux"
}
在这里,我们看到.parts
数组中的每个值现在都产生了输出中的一个对象,而不仅仅是之前的简单字符串。{name}
语法实际上是{name: .name}
的简写。完整的语法使您更容易看到如何控制输出中的属性名称 - 例如,{part_name: .name}
。使用完整的语法,我们还可以看到属性值是另一个过滤器。在这个例子中,我们使用了简单的.name
过滤器,但我们也可以使用更丰富的过滤器来构建:
$ cat ./wsl-book.json | jq '.parts[] | {name: .name, chapter_count: .chapters | length}'
{
"name": "Part 1: Introduction, Installation and Configuration",
"chapter_count": 3
}
{
"name": "Part 2: Windows and Linux - A Winning Combination",
"chapter_count": 5
}
{
"name": "Part 3: Developing with Windows Subsystem for Linux",
"chapter_count": 3
}
在这个例子中,我们添加了.chapters | length
作为一个过滤器来指定chapter_count
属性的值。.chapters
表达式被应用于当前正在处理的parts
数组的值,并选择chapters
数组,然后将其解析为length
函数,该函数返回数组长度。有关jq
中可用函数的更多信息,请查看 https://stedolan.github.io/jq/manual/#Builtinoperatorsandfunctions 上的文档。
作为jq
的最后一个例子,让我们汇总一下部分的摘要,显示部分名称以及章节标题的列表:
$ cat ./wsl-book.json | jq '[.parts[] | {name: .name, chapters: [.chapters[] | .title]}]'
[
{
"name": "Part 1: Introduction, Installation and Configuration",
"chapters": [
"Introduction to the Windows Subsystem for Linux",
"Installing and Configuring the Windows Subsystem for Linux",
"Getting Started with Windows Terminal"
]
},
{
"name": "Part 2: Windows and Linux - A Winning Combination",
"chapters": [
...
]
在这个例子中,parts
数组被传递到一个过滤器中,该过滤器为每个数组项创建一个带有name
和chapters
属性的对象。chapters
属性是通过将chapters
数组传递到title
属性的选择器中,然后将其包装在一个数组中来构建的:[.chapters[] | title]
。整个结果再次被包装在一个数组中(再次使用方括号)以在输出中创建这些摘要对象的 JSON 数组。
提示
有各种方法可以使用命令行工具(如jq
)查找选项。您可以运行jq --help
获取简要的帮助页面,或者运行man jq
查看完整的 man 页面。这些的一个方便的替代品是tldr
(有关更多详细信息和安装说明,请参见tldr.sh
)。tldr
实用程序将自己描述为简化和社区驱动的 man 页面,运行tldr jq
将提供比 man 页面更短的输出,并包含有用的示例。
这次风风火火的旅行向您展示了jq
提供的一些功能,无论是在与 JSON 交互式工作时格式化 JSON 输出以便阅读,还是快速选择 JSON 中的单个值以在脚本中使用,或者将 JSON 输入转换为新的 JSON 文档时。在处理 JSON 时,jq
是一个非常有用的工具,我们将在本章的后续部分中看到更多这方面的例子。
在下一节中,我们将探索使用PowerShell处理 JSON 数据的选项。
使用 PowerShell 处理 JSON
在本节中,我们将探讨 PowerShell 为我们提供的一些处理 JSON 数据的能力。PowerShell 是一个起源于 Windows 但现在可用于 Windows、Linux 和 macOS 的 shell 和脚本语言。要在 WSL 中安装,请按照您的发行版的安装说明进行安装,网址为 https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-linux?view=powershell-7。例如,对于 Ubuntu 18.04,我们可以使用以下命令安装 PowerShell:
# Download the Microsoft repository GPG keys wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb
# Register the Microsoft repository GPG keys sudo dpkg -i packages-microsoft-prod.deb
# Update the list of products sudo apt-get update
# Enable the "universe" repositories sudo add-apt-repository universe
# Install PowerShell sudo apt-get install -y powershell
这些步骤将注册 Microsoft 软件包存储库,然后从那里安装 PowerShell。安装完成后,您可以通过运行pwsh
来启动 PowerShell,这将为您提供一个交互式的 shell,我们将在本节的其余示例中使用它。
我们可以按如下方式加载和解析示例 JSON 文件:
PS > Get-Content ./wsl-book.json | ConvertFrom-Json
title parts
----- -----
WSL: Tips, Tricks and Techniques {@{name=Part 1: Introduction, Installation and Configuration; chapters=System.Object[…
在这里,我们看到了Get-Content
cmdlet(PowerShell 中的命令称为ConvertFrom-Json
用于将 JSON 对象图解析为 PowerShell 对象。在这一点上,我们可以使用任何用于处理数据的 PowerShell 功能。例如,我们可以使用Select-Object
cmdlet 获取标题:
PS > Get-Content ./wsl-book.json | ConvertFrom-Json | Select-Object -ExpandProperty title
WSL: Tips, Tricks and Techniques
Select-Object
命令允许我们对一组对象执行各种操作,例如从集合的开头或结尾获取指定数量的项目,或者仅筛选唯一的项目。在这个例子中,我们使用它来选择要输出的输入对象的属性。获取标题的另一种方法是直接使用转换后的 JSON 对象,如下面的命令所示:
PS > $data = Get-Content ./wsl-book.json | ConvertFrom-Json
PS > $data.title
WSL: Tips, Tricks and Techniques
在这个例子中,我们保存了将数据从 JSON 转换为$data
变量的结果,然后直接访问了title
属性。现在我们有了$data
变量,我们可以探索parts
属性:
PS > $data.parts | Select-Object -ExpandProperty name
Part 1: Introduction, Installation and Configuration
Part 2: Windows and Linux - A Winning Combination
Part 3: Developing with Windows Subsystem for Linux
在这个例子中,我们直接访问parts
属性,这是一个对象数组。然后我们将这个对象数组传递给Select-Object
来展开每个部分的name
属性。如果我们想生成 JSON 输出(就像在上一节中使用jq
一样),我们可以使用ConvertTo-Json
命令:
PS > $data.parts | select -ExpandProperty name | ConvertTo-Json
[
"Part 1: Introduction, Installation and Configuration",
"Part 2: Windows and Linux - A Winning Combination",
"Part 3: Developing with Windows Subsystem for Linux"
]
在这里,我们使用了与上一个示例中相同的命令(尽管我们使用了select
别名作为Select-Object
的简洁形式),然后将输出传递给ConvertTo-Json
命令。这个命令执行与ConvertFrom-Json
相反的操作-换句话说,它将一组 PowerShell 对象转换为 JSON。
如果我们想要输出带有部分名称的 JSON 对象,可以使用以下命令:
PS > $data.parts | ForEach-Object { @{ "Name" = $_.name } } | ConvertTo-Json
[
{
"Name": "Part 1: Introduction, Installation and Configuration"
},
{
"Name": "Part 2: Windows and Linux - A Winning Combination"
},
{
"Name": "Part 3: Developing with Windows Subsystem for Linux"
}
]
在这里,我们使用ForEach-Object
而不是Select-Object
。ForEach-Object
命令允许我们为输入数据中的每个对象提供一个 PowerShell 片段,并且$_
变量包含每次执行的集合中的项目。在ForEach-Object
内部的片段中,我们使用@{ }
语法创建一个名为Name
的新 PowerShell 对象属性,该属性设置为当前输入对象的name
属性(在这种情况下是部分名称)。最后,我们将生成的对象集传递给ConvertTo-Json
以转换为 JSON 输出。
我们可以使用这种方法来构建更丰富的输出-例如,包括部分名称以及它包含的章节数:
PS > $data.parts | ForEach-Object { @{ "Name" = $_.name; "ChapterCount"=$_.chapters.Count } } | ConvertTo-Json
[
{
"ChapterCount": 3,
"Name": "Part 1: Introduction, Installation and Configuration"
},
{
"ChapterCount": 5,
"Name": "Part 2: Windows and Linux - A Winning Combination"
},
{
"ChapterCount": 3,
"Name": "Part 3: Developing with Windows Subsystem for Linux"
}
]
在这个例子中,我们扩展了ForEach-Object
内部的片段到@{ "Name" = $_.name; "ChapterCount"=$_.chapters.Count }
。这将创建一个具有两个属性的对象:Name
和ChapterCount
。chapters
属性是一个 PowerShell 数组,因此我们可以使用数组的Count
属性作为输出中ChapterCount
属性的值。
如果我们想要输出每个部分的章节名称的摘要,我们可以结合我们迄今为止看到的方法:
PS > $data.parts | ForEach-Object { @{ "Name" = $_.name; "Chapters"=$_.chapters | Select-Object -ExpandProperty title } } | ConvertTo-Json
[
{
"Chapters": [
"Introduction to the Windows Subsystem for Linux",
"Installing and Configuring the Windows Subsystem for Linux",
"Getting Started with Windows Terminal"
],
"Name": "Part 1: Introduction, Installation and Configuration"
},
{
"Chapters": [
...
],
"Name": "Part 2: Windows and Linux - A Winning Combination"
},
...
]
在这里,我们再次使用ForEach-Object
命令来创建 PowerShell 对象,这次使用Name
和Chapters
属性。要创建Chapters
属性,我们只需获取每个章节的名称,我们可以像在本节前面选择部分名称时一样使用Select-Object
命令,但这次我们将其用在ForEach-Object
片段内。能够以这种方式组合命令使我们具有很大的灵活性。
在之前的例子中,我们一直在处理使用Get-Content
从本地文件加载的数据。要从 URL 下载数据,PowerShell 提供了一些方便的命令:Invoke-WebRequest
和Invoke-RestMethod
。
我们可以使用Invoke-WebRequest
从 GitHub 下载示例数据:
$SAMPLE_URL="https://raw.githubusercontent.com/PacktPublishing/Windows-Subsystem-for-Linux-2-WSL-2-Tips-Tricks-and-Techniques/main/chapter-11/02-working-with-json/wsl-book.json"
PS > Invoke-WebRequest $SAMPLE_URL
StatusCode : 200
StatusDescription : OK
Content : {
"title": "WSL: Tips, Tricks and Techniques",
"parts": [
{
"name": "Part 1: Introduction, Installation and Configuration",
"chapters": [
{
…
RawContent : HTTP/1.1 200 OK
Connection: keep-alive
Cache-Control: max-age=300
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
ETag: "075af59ea4d9e05e6efa0b4375b3da2f8010924311d487d…
Headers : {[Connection, System.String[]], [Cache-Control, System.String[]], [Content-Security-Policy, System.String[]], [ETag, System.Strin g[]]…}
Images : {}
InputFields : {}
Links : {}
RawContentLength : 4825
RelationLink : {}
在这里,我们看到Invoke-WebRequest
使我们可以访问响应的各种属性,包括状态代码和内容。要将数据加载为 JSON,我们可以将Content
属性传递给ConvertFrom-JSON
:
PS > (iwr $SAMPLE_URL).Content | ConvertFrom-Json
title parts
----- -----
WSL: Tips, Tricks and Techniques {@{name=Part 1: Introduction, Installation and Configuration; chapters=System.Object[]}, @{name=Part 2: Windows and…
在这个例子中,我们使用了iwr
别名作为Invoke-WebRequest
的简写,这在交互式工作时可能很方便。我们本可以将Invoke-WebRequest
的输出传递给Select-Object
来展开Content
属性,就像我们之前看到的那样。相反,我们将表达式括在括号中,直接访问属性以显示另一种语法。然后将此内容传递给ConvertFrom-Json
,将数据转换为 PowerShell 对象,就像我们之前看到的那样。这种可组合性很方便,但如果您只对 JSON 内容感兴趣(而不关心响应的其他属性),那么您可以使用Invoke-RestMethod
命令来实现这一点:
PS > Invoke-RestMethod $SAMPLE_URL
title parts
----- -----
WSL: Tips, Tricks and Techniques {@{name=Part 1: Introduction, Installation and Configuration; chapters=System.Object[]}, @{name=Part 2: Windows and…
在这里,我们看到与之前相同的输出,因为Invoke-RestMethod
命令已经确定响应包含 JSON 数据,并自动执行了转换。
总结使用 JSON
在最后两节中,您已经看到了jq
和 PowerShell 如何为您提供处理 JSON 输入的丰富功能。在每种情况下,您已经看到如何提取简单的值,并执行更复杂的操作以生成新的 JSON 输出。由于 JSON 在 API 和 CLI 中被广泛使用,能够有效地处理 JSON 是一个巨大的生产力提升,正如我们将在本章的其余部分中看到的那样。在本章的其余部分,我们将在需要额外工具来处理 JSON 的示例中使用jq
,但请注意,您也可以使用 PowerShell 来实现这一点。
在下一节中,我们将看到如何将处理 JSON 的技术与另一个命令行工具结合使用,这次是一些处理 Azure CLI 的技巧。
使用 Azure CLI(az)
云计算的推动带来了许多好处,其中包括能够按需创建计算资源的能力。能够自动化创建、配置和删除这些资源是利益的关键部分,这通常是使用相关云供应商提供的 CLI 来执行的。
在本节中,我们将从命令行创建和发布一个简单的网站,并将其作为查看使用 Azure CLI(az)的一些技巧的方式。我们将看到如何使用本章前面看到的jq
以及az
的内置查询功能。如果您想跟着做,但还没有 Azure 订阅,您可以在 https://azure.microsoft.com/free/免费试用。让我们开始安装 CLI。
安装和配置 Azure CLI
安装 Azure CLI 有多种选项。最简单的方法是在您想要安装 CLI 的 WSL 分发中打开终端,并运行以下命令:
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
此命令下载安装脚本并在 bash 中运行。如果您不想直接从互联网运行脚本,您可以先下载脚本并检查它,或者在这里查看单独的安装步骤:docs.microsoft.com/en-us/cli/azure/install-azure-cli-apt?view=azure-cli-latest
。
安装完成后,您应该能够从终端运行az
。要连接到您的 Azure 订阅,以便管理它,请运行az login
:
$ az login
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code D3SUM9QVS to authenticate.
在az login
命令的输出中,您可以看到az
生成了一个代码,我们可以通过访问microsoft.com/devicelogin
来使用该代码登录。在浏览器中打开此网址,并使用您用于 Azure 订阅的帐户登录。在这样做后不久,az login
命令将输出您的订阅信息并完成运行。
如果您有多个订阅,可以使用az account list
列出它们,并使用az account set --subscription YourSubscriptionNameOrId
选择要使用的默认订阅。
现在我们已经登录,可以开始运行命令了。在 Azure 中,资源存放在资源组(逻辑容器)中,所以让我们列出我们的组:
$ az group list
[]
在这里,命令的输出显示订阅中当前没有资源组。请注意,输出是[]
- 一个空的 JSON 数组。默认情况下,az
将结果输出为 JSON,因此对具有一些现有资源组的订阅运行先前的命令会给我们以下输出:
$ az group list
[
{
"id": "/subscriptions/36ce814f-1b29-4695-9bde-1e2ad14bda0f/resourceGroups/wsltipssite",
"location": "northeurope",
"managedBy": null,
"name": "wsltipssite",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null,
"type": "Microsoft.Resources/resourceGroups"
},
...
]
前面的输出已经被截断,因为它变得非常冗长。幸运的是,az
允许我们从多种输出格式中进行选择,包括表格:
$ az group list -o table
Name Location Status
----------- ----------- ---------
wsltipssite northeurope Succeeded
wsltipstest northeurope Succeeded
在这个输出中,我们使用了-o table
开关来配置表格输出。这种输出格式更简洁,通常对 CLI 的交互使用非常方便,但是不得不不断地在命令中添加开关可能会很繁琐。幸运的是,我们可以通过运行az configure
命令将表格输出设置为默认值。这将为您提供一组简短的交互选择,包括默认使用的输出格式。由于默认输出格式可以被覆盖,因此在脚本中指定 JSON 输出是很重要的,以防用户配置了不同的默认值。
有关使用az
的更多示例,包括如何在 Azure 中创建各种资源,请参阅docs.microsoft.com/cli/azure
上的示例部分。在本节的其余部分,我们将看一些关于使用 CLI 查询有关资源信息的具体示例。
创建 Azure Web 应用程序
为了演示使用az
进行查询,我们将创建一个简单的 Azure Web 应用程序。Azure Web 应用程序允许您托管用各种语言编写的 Web 应用程序(包括.NET、Node.js、PHP、Java 和 Python),并且有许多部署选项可供您根据自己的喜好选择。为了确保我们专注于 CLI 的使用,我们将保持简单,创建一个单页面静态网站,并通过 FTP 部署它。要了解更多关于 Azure Web 应用程序的信息,请参阅docs.microsoft.com/en-us/azure/app-service/overview
上的文档。
在创建 Web 应用程序之前,我们需要创建一个资源组:
az group create \
--name wsltips-chapter-11-03 \
--location westeurope
在这里,我们使用az group create
命令创建一个资源组来包含我们将创建的资源。请注意,我们使用了行继续字符(\
)将命令分割成多行以便阅读。要运行 Web 应用程序,我们需要一个 Azure 应用服务计划来托管它,所以我们将首先创建它:
az appservice plan create \
--resource-group wsltips-chapter-11-03 \
--name wsltips-chapter-11-03 \
--sku FREE
在这个片段中,我们使用az appservice plan create
命令在我们刚刚创建的资源组中创建了一个免费的托管计划。现在,我们可以使用该托管计划创建 Web 应用程序:
WEB_APP_NAME=wsltips$RANDOM
az webapp create \
--resource-group wsltips-chapter-11-03 \
--plan wsltips-chapter-11-03 \
--name $WEB_APP_NAME
在这里,我们为网站生成一个随机名称(因为它需要是唯一的),并将其存储在WEB_APP_NAME
变量中。然后我们使用az webapp create
命令。一旦这个命令完成,我们就创建了一个新的网站,并准备好开始使用az
CLI 进行查询。
查询单个值
我们要查询 Web 应用程序的第一件事是它的 URL。我们可以使用az webapp show
命令列出我们的 Web 应用程序的各种属性:
$ az webapp show \
--name $WEB_APP_NAME \
--resource-group wsltips-chapter-11-03 \
--output json
{
"appServicePlanId": "/subscriptions/67ce421f-bd68-463d-85ff-e89394ca5ce6/resourceGroups/wsltips-chapter-11-02/providers/Microsoft.Web/serverfarms/wsltips-chapter-11-03",
"defaultHostName": "wsltips28126.azurewebsites.net",
"enabled": true,
"enabledHostNames": [
"wsltips28126.azurewebsites.net",
"wsltips28126.scm.azurewebsites.net"
],
"id": "/subscriptions/67ce421f-bd68-463d-85ff-e89394ca5ce6/resourceGroups/wsltips-chapter-11-02/providers/Microsoft.Web/sites/wsltips28126",
...
}
}
在这里,我们传递了--output json
开关,以确保无论配置了什么默认格式,我们都能获得 JSON 输出。在这个简化的输出中,我们可以看到有一个defaultHostName
属性,我们可以用它来构建我们网站的 URL。
提取defaultHostName
属性的一种方法是使用jq
,就像我们在使用 jq部分中看到的那样:
$ WEB_APP_URL=$(az webapp show \
--name $WEB_APP_NAME \
--resource-group wsltips-chapter-11-03 \
--output json \
| jq ".defaultHostName" -r)
在这个片段中,我们使用jq
选择defaultHostName
属性,并传递-r
开关以获得原始输出,避免它被引用,然后将其分配给WEB_APP_URL
属性,以便我们可以在其他脚本中使用它。
az
CLI 还包括使用az
运行 JMESPath 查询并输出结果的内置查询功能:
$ WEB_APP_URL=$(az webapp show \
--name $WEB_APP_NAME \
--resource-group wsltips-chapter-11-03 \
--query "defaultHostName" \
--output tsv)
在这里,我们使用了--query
选项来传递"defaultHostName"
JMESPath 查询,它选择了defaultHostName
属性。我们还添加了--output tsv
来使用制表符分隔的输出,这样可以防止值被引号包裹。这检索了与之前使用jq
相同的值,但是使用了az
完成了所有操作。这在与他人共享脚本时很有用,因为它消除了对依赖的需求。
提示
您可以在jmespath.org
找到有关 JMESPath 的更多详细信息和交互式查询工具。有一个jp
CLI 用于运行 JMESPath 查询,可以从github.com/jmespath/jp
安装。此外,还有一个jpterm
CLI,它在您的终端中提供了一个交互式 JMESPath,可以从github.com/jmespath/jmespath.terminal
安装。
这些工具可以为构建查询时探索 JMESPath 提供一个不错的方式。以jpterm
为例:
az webapp show --name $WEB_APP_NAME --resource-group wsltips-chapter-11-03 --output json | jpterm
在这里,您可以看到将 JSON 输出传输到jpterm
,然后可以在终端中交互式地进行查询实验。
我们已经看到了通过az
检索主机名并将其存储在WEB_APP_URL
变量中的几种方法。现在,要么运行echo $WEB_APP_URL
来输出值并复制到您的浏览器中,要么运行wslview https://$WEB_APP_URL
从 WSL 启动浏览器(有关wslview
的更多详细信息,请参见第五章中的使用 wslview 启动默认 Windows 应用程序部分,Linux 到 Windows 互操作性):
图 11.6 - 显示 Azure Web 应用程序占位符站点的屏幕截图
在这个屏幕截图中,您可以看到通过az
CLI 查询的 URL 加载的占位符站点。接下来,让我们看一下在向 Web 应用程序添加一些内容时,我们将看到更复杂的查询需求。
查询和过滤多个值
现在我们已经创建了一个 Web 应用程序,让我们上传一个简单的 HTML 页面。使用 Azure Web 应用程序管理内容有许多选项(请参阅docs.microsoft.com/en-us/azure/app-service/
),但为简单起见,在本节中,我们将使用curl
通过 FTP 上传单个 HTML 页面。为此,我们需要获取 FTP URL 以及用户名和密码。可以使用az webapp deployment list-publishing-profiles
命令检索这些值:
$ az webapp deployment list-publishing-profiles \
--name $WEB_APP_NAME \
--resource-group wsltips-chapter-11-03 \
-o json
[
{
...
"publishMethod": "MSDeploy",
"publishUrl": "wsltips28126.scm.azurewebsites.net:443",
"userName": "$wsltips28126",
"userPWD": "evps3kT1Ca7a2Rtlqf1h57RHeHMo9TGQaAjE3hJDv426HKhnlrzoDvGfeirT",
"webSystem": "WebSites"
},
{
...
"publishMethod": "FTP",
"publishUrl": "ftp://waws-prod-am2-319.ftp.azurewebsites.windows.net/site/wwwroot",
"userName": "wsltips28126\\$wsltips28126",
"userPWD": "evps3kT1Ca7a2Rtlqf1h57RHeHMo9TGQaAjE3hJDv426HKhnlrzoDvGfeirT",
"webSystem": "WebSites"
}
]
这个截断的输出显示了输出中的 JSON 数组。我们需要的值在第二个数组项目中(具有publishMethod
属性设置为FTP
的项目)。让我们看看如何使用我们在上一节中看到的--query
方法来实现这一点:
PUBLISH_URL=$(az webapp deployment list-publishing-profiles \
--name $WEB_APP_NAME \
--resource-group wsltips-chapter-11-03 \
--query "[?publishMethod == 'FTP']|[0].publishUrl" \
--output tsv)
PUBLISH_USER=...
在这里,我们使用了一个 JMESPath 查询[?publishMethod == 'FTP']|[0].publishUrl
。我们可以将查询分解为几个部分:
-
[?publishMethod == 'FTP']
是过滤数组的语法,在这里我们将其过滤为仅返回包含值为FTP
的publishMethod
属性的项目。 -
前面查询的输出仍然是一个项目数组,所以我们使用
|[0]
将数组传输到数组选择器中,以获取第一个数组项目。 -
最后,我们使用
.publishUrl
来选择publishUrl
属性。
同样,我们使用了--output tsv
开关来避免结果被引号包裹。这个查询检索了发布 URL,我们可以重复查询,更改属性选择器以检索用户名和密码。
这种方法的一个缺点是我们向az
发出了三个查询,每个查询都返回我们需要的信息,但是丢弃了除一个值之外的所有值。在许多情况下,这是可以接受的,但有时我们需要的信息是从调用创建资源返回给我们的,在这种情况下,重复调用不是一个选项。在这些情况下,我们可以使用我们之前看到的jq
方法的轻微变体:
CREDS_TEMP=$(az webapp deployment list-publishing-profiles \
--name $WEB_APP_NAME \
--resource-group wsltips-chapter-11-03 \
--output json)
PUBLISH_URL=$(echo $CREDS_TEMP | jq 'map(select(.publishMethod =="FTP"))[0].publishUrl' -r)
PUBLISH_USER=$(echo $CREDS_TEMP | jq 'map(select(.publishMethod =="FTP"))[0].userName' -r)
PUBLISH_PASSWORD=$(echo $CREDS_TEMP | jq 'map(select(.publishMethod =="FTP"))[0].userPWD' -r)
在这里,我们存储了来自az
的 JSON 响应,而不是直接将其传输到jq
。然后我们可以多次将 JSON 传输到jq
中以选择我们想要检索的不同属性。通过这种方式,我们可以对az
进行单次调用,仍然捕获多个值。jq
查询map(select(.publishMethod =="FTP"))[0].publishUrl
可以以与我们刚刚看到的 JMESPath 查询类似的方式进行分解。查询的第一部分(map(select(.publishMethod =="FTP"))
)是选择数组中publishMethod
属性值为 FTP 的项目的jq
方式。查询的其余部分选择第一个数组项目,然后捕获publishUrl
属性以进行输出。
这里还有一个选项,我们将在这里看一下,这是--query
方法的一个变体,允许我们发出单个查询而不需要jq
:
CREDS_TEMP=($(az webapp deployment list-publishing-profiles \
--name $WEB_APP_NAME \
--resource-group wsltips-chapter-11-03 \
--query "[?publishMethod == 'FTP']|[0].[publishUrl,userName,userPWD]" \
--output tsv))
PUBLISH_URL=${CREDS_TEMP[0]}
PUBLISH_USER=${CREDS_TEMP[1]}
PUBLISH_PASSWORD=${CREDS_TEMP[2]}
这段代码建立在之前的--query
方法之上,但有一些不同之处需要注意。
首先,我们使用.[publishUrl,userName,userPWD]
而不是简单的.publishUrl
作为 JMESPath 查询中的最终选择器。这样做的结果是生成一个包含publishUrl
、userName
和userPWD
属性值的数组。
这些属性数组以制表符分隔的值输出,并且通过将az
命令的执行结果括在括号中来将结果视为 bash 数组:CREDS_TEMP=($(az...))
。
这两个步骤允许我们使用--query
从单个az
调用中返回多个值,并将结果存储在数组中。输出中的最后几行显示将数组项分配给命名变量以便于使用。
无论使用哪种选项来设置发布环境变量,我们现在可以从示例内容的chapter-11/03-working-with-az
文件夹中的终端上传index.html
文件:
curl -T index.html -u $PUBLISH_USER:$PUBLISH_PASSWORD $PUBLISH_URL/
在这里,我们使用curl
使用我们查询的 URL、用户名和密码将index.html
文件上传到 FTP。现在我们可以回到浏览器并重新加载页面。我们将得到以下结果:
图 11.7 - 屏幕截图显示带有我们上传内容的 Web 应用程序
这个屏幕截图显示了我们之前创建的 Web 应用程序现在返回了我们刚刚上传的简单 HTML 页面。
现在我们已经完成了我们创建的 Web 应用程序(和应用服务计划),我们可以删除它们:
az group delete --name wsltips-chapter-11-03
这个命令将删除我们一直在使用的wsltips-chapter-11-03
资源组以及其中创建的所有资源。
本节中的示例显示了使用curl
将单个页面 FTP 到我们创建的 Azure Web 应用程序,这为使用az
进行查询提供了一个方便的示例,但 Azure Web 应用程序提供了多种部署内容的选项-有关更多详细信息,请参阅以下文章:docs.microsoft.com/archive/msdn-magazine/2018/october/azure-deploying-to-azure-app-service-and-azure-functions
。值得注意的是,对于托管静态网站,Azure 存储静态网站托管可能是一个很好的选择。有关操作步骤,请参阅docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website-how-to?tabs=azure-cli
。
在本节中,您已经看到了使用az
CLI 进行查询的多种方法。您已经了解了如何将默认输出设置为表格格式,以便进行可读的交互式查询。在脚本编写时,您已经了解了如何使用 JSON 输出并使用jq
进行处理。您已经学会了如何使用--query
开关进行 JMESPath 查询,以便使用az
命令直接过滤和选择响应中的值。在本节中,我们只看了az
CLI(用于 Web 应用程序)的一个狭窄片段-如果您有兴趣探索更多az
的广度,请参阅docs.microsoft.com/cli/azure
。
在下一节中,我们将看看另一个 CLI-这次是用于 Kubernetes 的 CLI。
使用 Kubernetes CLI(kubectl)
在构建容器化应用程序时,Kubernetes 是容器编排器的常见选择。有关 Kubernetes 的介绍,请参阅第七章,在 WSL 中设置 Kubernetes部分。Kubernetes 包括一个名为kubectl
的 CLI,用于从命令行处理 Kubernetes。在本节中,我们将在 Kubernetes 中部署一个基本网站,然后查看使用kubectl
查询有关它的信息的不同方法。
在第七章,在 WSL 中使用容器部分,我们看到了如何使用 Docker Desktop 在本地机器上设置 Kubernetes。在这里,我们将探索使用云提供商设置 Kubernetes 集群。以下说明适用于 Azure,但如果您熟悉另一个具有 Kubernetes 服务的云,则可以使用该云。如果您想跟着操作,但尚未拥有 Azure 订阅,可以在 https://azure.microsoft.com/free/上注册免费试用。
让我们开始安装kubectl
。
安装和配置 kubectl
有多种选项可用于安装kubectl
(可以在kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-with-curl-on-linux
找到),但最简单的方法是从您的 WSL 分发版运行以下命令:
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl
这些命令下载最新的kubectl
二进制文件,将其标记为可执行文件,然后将其移动到您的bin
文件夹中。完成后,您应该能够运行kubectl version --client
来检查kubectl
是否正确安装:
$ kubectl version --client
Client Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.2", GitCommit:"f5743093fd1c663cb0cbc89748f730662345d44d", GitTreeState:"clean", BuildDate:"2020-09-16T13:41:02Z", GoVersion:"go1.15", Compiler:"gc", Platform:"linux/amd64"}
在这里,我们已经看到了来自kubectl
的输出,显示我们已安装版本v1.19.2
。
kubectl
实用程序有各种命令,并且启用 bash 自动补全可以使您更加高效。要做到这一点,请运行以下命令:
echo 'source <(kubectl completion bash)' >>~/.bashrc
这将在您的.bashrc
文件中添加一个命令,以便在 bash 启动时自动加载kubectl
bash 自动补全。要尝试它,请重新启动 bash 或运行source ~/.bashrc
。现在,您可以输入kubectl ver<TAB> --cli<TAB>
来获取以前的kubectl version --client
命令。
提示
如果您觉得kubectl
输入太多,可以通过运行以下命令创建一个别名:
echo 'alias k=kubectl' >>~/.bashrc
echo 'complete -F __start_kubectl k' >>~/.bashrc
这些命令将添加到.bashrc
中,以将k
配置为kubectl
的别名,并为k
设置 bash 自动补全。
有了这个,您可以使用命令,比如k version – client
,并且仍然可以获得 bash 自动补全。
现在我们已经安装和配置了kubectl
,让我们创建一个 Kubernetes 集群来使用它。
创建一个 Kubernetes 集群
以下说明将带您完成使用az
创建 Kubernetes 集群的过程。如果您还没有安装az
,请参考本章前面的安装和配置 Azure CLI部分。
第一步是创建一个资源组来包含我们的集群:
az group create \
--name wsltips-chapter-11-04 \
--location westeurope
在这里,我们正在创建一个名为wsltips-chapter-11-04
的资源组,位于westeurope
区域。
接下来,我们创建 AKS 集群:
az aks create \
--resource-group wsltips-chapter-11-04 \
--name wsltips \
--node-count 2 \
--generate-ssh-keys
此命令在我们刚创建的资源组中创建了一个名为wsltips
的集群。此命令将需要几分钟来运行,当它完成后,我们将拥有一个运行有两个工作节点的 Kubernetes 集群,我们可以在其中运行我们的容器工作负载。
最后一步是设置kubectl
,以便它可以连接到集群:
az aks get-credentials \
--resource-group wsltips-chapter-11-04 \
--name wsltips
在这里,我们使用az aks get-credentials
来获取我们创建的集群的凭据,并将它们保存在kubectl
的配置文件中。
现在,我们可以运行诸如kubectl get services
之类的命令来列出已定义的服务:
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 7m
此输出显示了我们在创建的集群中 Kubernetes 服务的列表,证明我们已成功连接到了集群。
现在我们有了一个 Kubernetes 集群,并且kubectl
已配置好连接到它,让我们将一个测试网站部署到它上面。
部署基本网站
为了帮助探索kubectl
,我们将部署一个基本网站。然后我们可以使用它来查看使用kubectl
查询信息的不同方式。
该书的附带代码包含了一个用于此部分的文件夹,其中包含了 Kubernetes YAML 文件。您可以从github.com/PacktPublishing/Windows-Subsystem-for-Linux-2-WSL-2-Tips-Tricks-and-Techniques
获取此代码。此部分的内容位于chapter-11/04-working-with-kubectl
文件夹中。manifests
文件夹包含了一些定义要部署的 Kubernetes 资源的 YAML 文件:
-
包含一个简单 HTML 页面的ConfigMap
-
一个
nginx
镜像,并配置它从 ConfigMap 加载 HTML 页面 -
一个
nginx
部署
要部署网站,请启动您的 WSL 发行版并导航到chapter-11/04-working-with-kubectl
文件夹。然后运行以下命令:
$ kubectl apply -f manifests
configmap/nginx-html created
deployment.apps/chapter-11-04 created
service/chapter-11-04 created
在这里,我们使用kubectl apply -f manifests
来创建manifests
文件夹中的 YAML 文件描述的资源。命令的输出显示已创建的三个资源。
现在,我们可以运行kubectl get services chapter-11-04
来查看已创建服务的状态:
$ kubectl get services chapter-11-04
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
chapter-11-04 LoadBalancer 10.0.21.171 <pending> 80:32181/TCP 3s
在这里,我们看到chapter-11-04
服务的类型是LoadBalancer
。在 AKS 中,LoadBalancer
服务将自动使用Azure 负载均衡器暴露,并且这可能需要一些时间来进行配置 - 请注意输出中EXTERNAL_IP
的<pending>
值,显示负载均衡器正在进行配置的过程。在下一节中,我们将看看如何查询此 IP 地址。
使用 JSONPath 查询
正如我们刚才看到的,创建服务后立即获得服务的外部 IP 地址是不可用的,因为 Azure 负载均衡器需要进行配置和配置。我们可以通过以 JSON 格式获取服务输出来看到底层数据结构是什么样子的:
$ kubectl get services chapter-11-04 -o json
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "chapter-11-04",
"namespace": "default",
...
},
"spec": {
...
"type": "LoadBalancer"
},
"status": {
"loadBalancer": {}
}
}
在这里,我们看到了应用-o json
选项的截断 JSON 输出。请注意status
下loadBalancer
属性的空值。如果我们等待一会儿然后重新运行命令,我们会看到以下输出:
"status": {
"loadBalancer": {
"ingress": [
{
"ip": "20.50.162.63"
}
]
}
}
在这里,我们可以看到loadBalancer
属性现在包含一个带有 IP 地址数组的ingress
属性。
我们可以使用kubectl
的内置jsonpath
功能直接查询 IP 地址:
$ kubectl get service chapter-11-04 \
-o jsonpath="{.status.loadBalancer.ingress[0].ip}"
20.50.162.63
在这里,我们使用了-o jsonpath
来提供一个 JSONPath 查询:{.status.loadBalancer.ingress[0].ip}
。此查询直接映射到我们要查询的 JSON 结果的路径。有关 JSONPath 的更多详细信息(包括在线交互式评估器),请参见jsonpath.com/
。这种技术在脚本中很方便使用,附带的代码中有一个scripts/wait-for-load-balancer.sh
脚本,它等待负载均衡器进行配置,然后输出 IP 地址。
直接在kubectl
中使用 JSONPath 很方便,但与jq
相比,JSONPath 可能有一定的局限性,有时我们需要进行切换。接下来我们将看一个这样的场景。
扩展网站
我们刚刚创建的部署只运行一个nginx
Pod 的实例。我们可以通过运行以下命令来查看:
$ kubectl get pods -l app=chapter-11-04
NAME READY STATUS RESTARTS AGE
chapter-11-04-f4965d6c4-z425l 1/1 Running 0 10m
在这里,我们列出了与deployment.yaml
中指定的app=chapter-11-04
标签选择器匹配的 Pods。
Kubernetes 部署资源提供的一个功能是轻松地扩展部署的 Pod 数量:
$ kubectl scale deployment chapter-11-04 --replicas=3
deployment.apps/chapter-11-04 scaled
在这里,我们指定要扩展的部署和我们想要将其扩展到的实例数(replicas
)。如果我们再次查询 Pods,现在将看到三个实例:
$ kubectl get pods -l app=chapter-11-04
NAME READY STATUS RESTARTS AGE
chapter-11-04-f4965d6c4-dptkt 0/1 Pending 0 12s
chapter-11-04-f4965d6c4-vxmks 1/1 Running 0 12s
chapter-11-04-f4965d6c4-z425l 1/1 Running 0 11
此输出列出了部署的三个 Pod,但请注意其中一个处于Pending
状态。原因是部署定义要求每个 Pod 使用完整的 CPU,但集群只有两个工作节点。虽然每个节点运行的机器都有两个 CPU,但其中一些是为工作节点进程本身保留的。尽管这种情况是故意构造出来的,以说明使用kubectl
进行查询,但遇到类似问题是很常见的。
找到一个未运行的 Pod 后,我们可以进一步调查它:
$ kubectl get pod chapter-11-04-f4965d6c4-dptkt -o json
{
"metadata": {
...
"name": "chapter-11-04-f4965d6c4-dptkt",
"namespace": "default",
},
...
"status": {
"conditions": [
{
"lastTransitionTime": "2020-09-27T19:01:07Z",
"message": "0/2 nodes are available: 2 Insufficient cpu.",
"reason": "Unschedulable",
"status": "False",
"type": "PodScheduled"
}
],
}
}
在这里,我们请求了未运行的 Pod 的 JSON,并且截断的输出显示了一个conditions
属性。这里有一个条目表明 Pod 无法被调度(也就是说,Kubernetes 找不到集群中的任何位置来运行它)。在下一节中,我们将编写一个查询,从 Pod 列表中查找任何无法被调度的 Pod。
使用 jq 进行查询
让我们看看如何编写一个查询,查找具有type
为PodScheduled
且status
设置为False
的条件的任何 Pod。首先,我们可以使用以下命令获取 Pod 的名称:
$ kubectl get pods -o json | \
jq '.items[] | {name: .metadata.name}'
{
"name": "chapter-11-04-f4965d6c4-dptkt"
}
{
"name": "chapter-11-04-f4965d6c4-vxmks"
}
...
在这里,我们将kubectl
的 JSON 输出传递给jq
,并使用选择器提取输入items
数组中每个项目的metadata.name
作为输出中的name
属性。这使用了我们在本章前面看到的相同技术-有关更多详细信息,请参阅使用 jq部分。
接下来,我们想要包括status
属性中的条件:
$ kubectl get pods -o json | \
jq '.items[] | {name: .metadata.name, conditions: .status.conditions} '
{
"name": "chapter-11-04-f4965d6c4-dptkt",
"conditions": [
{
"lastProbeTime": null,
"lastTransitionTime": "2020-09-27T19:01:07Z",
"message": "0/2 nodes are available: 2 Insufficient cpu.",
"reason": "Unschedulable",
"status": "False",
"type": "PodScheduled"
}
]
}{
...
}
在这里,我们包含了所有的条件,但由于我们只想要那些尚未被调度的条件,我们只想包括特定的条件。为此,我们可以使用jq
的select
过滤器,它处理一个值数组,并通过那些匹配指定条件的值。在这里,我们将使用它来过滤状态条件,只包括那些type
设置为PodScheduled
且status
设置为False
的条件:
$ kubectl get pods -o json | \
jq '.items[] | {name: .metadata.name, conditions: .status.conditions[] | select(.type == "PodScheduled" and .status == "False")}'
{
"name": "chapter-11-04-f4965d6c4-dptkt",
"conditions": {
"lastProbeTime": null,
"lastTransitionTime": "2020-09-27T19:01:07Z",
"message": "0/2 nodes are available: 2 Insufficient cpu.",
"reason": "Unschedulable",
"status": "False",
"type": "PodScheduled"
}
}
在这里,我们将select(.type == "PodScheduled" and .status == "False")
应用于被分配给conditions
属性的条件集。查询的结果只是具有失败状态条件的单个项目。
我们可以对查询进行一些最后的微调:
$ kubectl get pods -o json | \
jq '[.items[] | {name: .metadata.name, conditions: .status.conditions[] | select(.type == "PodScheduled" and .status == "False")} | {name, reason: .conditions.reason, message: .conditions.message}]'
[
{
"name": "chapter-11-04-f4965d6c4-dptkt",
"reason": "Unschedulable",
"message": "0/2 nodes are available: 2 Insufficient cpu."
}
]
在这里,我们对选择器进行了一些最后的更新。第一个是将先前选择器的结果传递到{name, reason: .conditions.reason, message: .conditions.message}
,以仅提取我们感兴趣的字段,使输出更易于阅读。第二个是将整个选择器包装在方括号中,以便输出是一个 JSON 数组。这样,如果有多个无法调度的 Pod,我们将获得有效的输出,如果需要,可以进一步处理。
如果您经常使用这个命令,您可能希望将其保存为一个 bash 脚本,甚至将其添加到您的.bashrc
文件中作为别名:
alias k-unschedulable="kubectl get pods - json | jq '[.items[] | {name: .metadata.name, conditions: .status.conditions[] | select(.type == \"PodScheduled\" and .status == \"False\")} | {name, reason: .conditions.reason, message: .conditions.message}]'"
在这里,我们为列出无法调度的 Pod 的命令创建了一个k-unschedulable
别名。请注意,引号("
)已经用反斜杠(\"
)进行了转义。
这种技术可以应用于 Kubernetes 中的各种资源。例如,Kubernetes 中的节点具有状态条件,指示节点是否正在耗尽内存或磁盘空间,可以修改此查询以便轻松识别这些节点。
总的来说,我们遵循了一个通用的模式,首先是获取您感兴趣的资源的 JSON 输出。从那里开始,如果您想要检索的值是一个简单的值,那么 JSONPath 方法是一个值得考虑的好方法。对于更复杂的过滤或输出格式化,jq
是您工具包中一个方便的工具。Kubernetes 为其资源提供了丰富的信息,熟练使用kubectl
及其 JSON 输出可以为您提供强大的查询能力。
现在我们已经完成了集群,我们可以删除包含的资源组:
az group delete --name wsltips-chapter-11-04
这个命令将删除我们一直在使用的wsltips-chapter-11-04
资源组以及其中创建的所有资源。
在本节中,您已经涵盖了从设置kubectl
的 bash 完成到在 Kubernetes 集群中使用kubectl
查询资源信息的方法。无论您是为特定资源查询单个值还是在资源集上过滤数据,使用这里的技术都为脚本化工作流程的步骤打开了巨大的机会。
总结
在本章中,您看到了如何改进在 WSL 中使用 Git 的方式。您看到了如何配置 Windows 的 Git 凭据管理器,以便在 WSL 中重用保存的 Git 凭据,并在需要新的 Git 凭据时在 Windows 中提示您。之后,您看到了一系列查看 Git 历史记录的选项,讨论了它们的优缺点,以便您选择适合您的正确方法。
在本章的其余部分,您了解了如何在 WSL 中处理 JSON 数据,首先是深入了解jq
和 PowerShell 的 JSON 功能。有了这个背景,您还看到了一些使用az
和kubectl
进行部署的 JSON 工作示例。除了涵盖每个 CLI 可能面临的场景外,示例还演示了可以应用于提供 JSON 数据的其他 CLI(或 API)的技术。能够有效地处理 JSON 数据为您提供了强大的能力,可以在脚本中使用,节省您的时间。
这是本书的最后一章,我希望我已经成功地传达了我对 WSL 2 以及它带来的可能性的一些兴奋。在 Windows 上享受 Linux 吧!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」