WSL2-提示和技巧-全-

WSL2 提示和技巧(全)

原文:zh.annas-archive.org/md5/5EBC4B193F90421D3484B13463D11C33

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

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 子系统 LinuxWSL)的一些用例,并开始对 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 二进制文件,至少可以从Cygwincygwin.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 子系统 LinuxWSL 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 翻译层的概述

图 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 架构的概要

图 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 子系统 LinuxWSL)不是默认安装的,因此开始使用它的第一步将是安装它以及您选择的 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 版本对话框

图 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 功能选项

图 2.2-启动 Windows 功能选项

当 Windows 功能对话框出现时,请勾选虚拟机平台Windows 子系统 Linux的复选框,如下图所示:

图 2.3-WSL 版本 2 所需的 Windows 功能

图 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 发行版

图 2.4-在 Microsoft Store 中搜索 Linux 发行版

当您找到想要的发行版时,请按照以下步骤进行操作:

  1. 单击它,然后单击安装。然后,商店应用程序将为您下载和安装发行版。

  2. 安装完成后,您可以点击启动按钮来运行。这将开始您选择的发行版的设置过程,如图所示(以 Ubuntu 为例)。

  3. 在设置过程中,您将被要求输入 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 版本;您可以看到同时支持12。详细输出还显示了每个发行版是否正在运行。它还在默认发行版旁边包含了一个星号(*)。

除了获取有关 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)需要是绝对路径,并且反斜杠(\\)需要转义显示。

还有其他选项(例如kernelkernelCommandLine),允许您指定自定义内核或其他内核参数,这超出了本书的范围,但可以在文档中找到: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 用户体验的屏幕截图

图 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 终端中多个选项卡的屏幕截图

图 3.2 - 显示 Windows 终端中多个选项卡的屏幕截图

除了每个窗口有多个选项卡外,Windows 终端还支持将选项卡分割为多个窗格。与选项卡不同,只有一个选项卡可见,而窗格可以将选项卡细分为多个部分。图 3.3显示了 Windows 终端中具有多个窗格的情况,其中混合了在 WSL2 中运行的 Bash 和在 Windows 中运行的 PowerShell:

图 3.3 - 显示 Windows 终端中多个窗格的屏幕截图

图 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 终端

图 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-显示用于创建新选项卡的配置文件下拉菜单的屏幕截图

图 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 + ,其中是您要导航到的标签页的位置,例如,Ctrl + Alt + 3导航到第三个标签页。最后,您可以使用Ctrl + Shift + W关闭标签页。

使用键盘可以快速管理 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 - 显示初始配置文件顺序的屏幕截图

图 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 - 显示加载设置时出现错误的示例屏幕截图

图 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.9 - 显示使用表情选择器的屏幕截图

从列表中选择一个表情符号将其插入到编辑器中,如下图所示:

图 3.10 - 显示已完成的 PowerShell 配置文件的屏幕截图

图 3.10 - 显示已完成的 PowerShell 配置文件的屏幕截图

在此屏幕截图中,您可以看到在name属性中使用了一个表情符号。除了更改名称外,设置还允许您自定义列表中配置文件旁边显示的图标。通过向配置文件添加一个图标属性来实现,该属性给出了您希望使用的图标的路径,如上一个屏幕截图所示。该图标可以是PNGJPGICO或其他文件类型 - 我倾向于使用PNG,因为它在各种编辑器中易于使用,并允许图像的透明部分。

值得注意的是,路径需要将反斜杠(\)转义为双反斜杠(\\)。方便的是,您还可以在路径中使用环境变量。这使您可以将图标放在 OneDrive(或其他文件同步平台)中,并在多台机器上共享它们(或仅备份以供将来使用)。要使用环境变量,请将其用百分号括起来,如上面的代码片段中所示的%OneDrive%

这些自定义(图标和文本)的结果如下图所示:

图 3.11 - 显示自定义图标和文本(包括表情符号!)

图 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是相同的字体,但不包含连字。

可以通过在配置文件中设置fontFacefontSize属性来独立更改每个配置文件的字体,如下面的示例所示:

{
    "guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
    "hidden": false,
    "name": "PowerShell",
    "source": "Windows.Terminal.PowershellCore",
    "fontFace": "OpenDyslexicMono",
    "fontSize": 16
},

如果您想为所有配置文件自定义字体设置,可以在defaults部分中添加fontFacefontSize属性,如下面的代码片段所示:

"profiles": {
    "defaults": {
        // Put settings here that you want to apply to all profiles.
        "fontFace": "OpenDyslexicMono",
        "fontSize": 16
    },

defaults部分指定的设置适用于所有配置文件,除非配置文件覆盖它。现在我们已经了解了如何更改字体,让我们来看看如何控制颜色方案。

更改颜色

Windows Terminal 允许您以几种方式自定义配置文件的颜色方案。

最简单的自定义是在配置文件中使用foregroundbackgroundcursorColor属性。这些值以#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$的截图

](https://gitee.com/OpenDocCN/freelearn-linux-zh/raw/master/docs/wsl2-tip-trk-tech/img/B16412_04_01.jpg)

图 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 发行版内容的截图

](https://gitee.com/OpenDocCN/freelearn-linux-zh/raw/master/docs/wsl2-tip-trk-tech/img/B16412_04_02.jpg)

图 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 文件的截图

](https://gitee.com/OpenDocCN/freelearn-linux-zh/raw/master/docs/wsl2-tip-trk-tech/img/B16412_04_03.jpg)

图 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 不太熟悉,不用担心):

  1. 首先,我们将使用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.
  1. 接下来,我们可以执行提取操作,例如提取文件名的第一个字母。我们可以通过将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.
  1. 下一步是对项目进行分组和计数。由于目标是演示在 Windows 和 Linux 之间进行输出导入,我们暂时忽略 PowerShell 的Group-Object cmdlet,而是使用一些常见的 Linux 实用工具:sortuniq。如果您在 Linux 中使用这些命令与其他输出一起使用,可以将其作为other-command | sort | uniq -c进行管道传输。然而,由于sortuniq是 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

从前面的输出中可以看到,我们现在只有第一个字符,并且我们已经准备好对它们进行排序和分组。为了进行这个练习,我们假装sortuniq命令不存在,而是使用 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.htmlrun.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 浏览器中的屏幕截图

图 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 列出文件夹内容

图 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 文件

图 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 的屏幕截图

图 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.txtnotepad.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 命名管道,我们将使用两个实用程序:socatnpiperelaysocat实用程序是一个强大的 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套接字并将其中继到npiperelaynpiperelay命令行告诉它监听并将其输入转发到//./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 终端的屏幕截图,打开了许多选项卡

图 6.1 - Windows 终端的屏幕截图,打开了许多选项卡

正如前面的屏幕截图所示,打开多个选项卡时,很难确定每个选项卡正在运行的内容以及您使用它的目的。当我编码时,我经常打开一个选项卡用于执行 Git 操作,另一个用于构建和运行代码,另一个用于在代码运行时与代码进行交互。除此之外,还有一个额外的选项卡用于一些常规系统交互,以及一个或两个选项卡用于查看其他项目中的问题。这样,选项卡的数量很快就增加了。

前面的屏幕截图显示,根据选项卡中运行的 shell,您可能会获得一些路径信息,但是如果在相同路径下有多个选项卡,即使这样也没有太大帮助,因为它们都显示相同的值。幸运的是,使用 Windows 终端,您可以设置选项卡标题以帮助您跟踪。我们将介绍几种不同的方法,以便您可以选择最适合您的方法。

从上下文菜单设置选项卡标题

设置标题的简单方法是右键单击选项卡标题,弹出上下文菜单,然后选择重命名选项卡

图 6.2 - 显示重命名选项卡的选项卡上下文菜单的屏幕截图

图 6.2 - 显示重命名选项卡的选项卡上下文菜单的屏幕截图

正如前面的屏幕截图所示,右键单击选项卡会弹出上下文菜单,允许您重命名选项卡或设置选项卡颜色以帮助组织您的选项卡:

图 6.3 - Windows 终端的屏幕截图,显示已重命名和带颜色的选项卡

图 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 函数的屏幕截图

图 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(注意分号),以指定要加载的新选项卡,包括任何其他参数,如titleprofile

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 终端中多个窗格的屏幕截图

图 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 - 展示启动配置文件下拉菜单的屏幕截图

图 6.6 - 展示启动配置文件下拉菜单的屏幕截图

此屏幕截图显示了用于选择要运行的配置文件的标准下拉菜单。与正常点击不同,按住Alt键并单击将在新窗格中启动所选配置文件。与Alt + Shift + D一样,Windows 终端将确定是水平拆分还是垂直拆分当前窗格。

另一个选项是使用 Windows 终端命令面板,使用Ctrl + Shift + P

图 6.7-屏幕截图显示命令面板中的拆分选项

图 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.exenew-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 - 屏幕截图显示带有命令行选项的命令面板

图 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,在这个示例配置文件中,您可以看到来自该章节的熟悉属性,如namecolorSchemecommandline属性是我们配置要运行的内容的地方,我们使用它来启动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 启动脚本运行

图 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 选项

图 7.1 - Docker 设置的屏幕截图显示 WSL 2 选项

上面的截图显示了“使用基于 WSL 2 的引擎”选项。确保选中此选项以配置 Docker Desktop 在 WSL 2 下运行,而不是传统的虚拟机。

您可以从“资源”部分选择 Docker Desktop 与哪些发行版集成:

图 7.2 - WSL 集成的 Docker 设置的屏幕截图

图 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

由于端口80nginx默认提供内容的端口,并且我们已将端口8080映射到该容器端口,因此我们可以在 Web 浏览器中打开http://localhost:8080,如下图所示:

图 7.3-浏览器显示 nginx 输出的屏幕截图

图 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-浏览器中显示示例应用程序的屏幕截图

图 7.4-浏览器中显示示例应用程序的屏幕截图

在此屏幕截图中,您可以看到示例应用程序的响应,显示应用程序正在运行的主机名(wfhome)。

现在您已经看到了示例应用程序的运行情况,我们将开始看如何将其打包为容器镜像。

介绍 Dockerfile

要构建一个镜像,我们需要能够向 Docker 描述镜像应该包含什么内容,为此,我们将使用一个 DockerfileDockerfile 包含了一系列命令,供 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 守护进程用于构建镜像的内容 - 当您将文件 ADDDockerfile 时,它会从构建上下文中复制。

这个命令的输出非常长,所以我们不会完整地包含它,我们只会看一些关键部分。

这个命令的初始输出来自 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 的输出,它正在安装 flaskgunicorn

在输出的末尾,我们看到了一些成功的消息:

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 浏览器中的容器化示例应用程序的屏幕截图

图 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,它代表 Kubernetes in Docker,专为测试 Kubernetes 而设计。只要您的构建工具可以运行 Docker 容器,它就可以作为在自动化构建中运行 Kubernetes 的一种好选择,用于集成测试的一部分。默认情况下,Kind 将创建一个单节点 Kubernetes 集群,但您可以配置它以运行多节点集群,其中每个节点都作为一个单独的容器运行(我们将在第十章“使用 Visual Studio Code 和容器”中看到如何使用 Kind 在开发容器中使用 Kubernetes)。

然而,在本章中,我们将使用 Docker Desktop 中内置的 Kubernetes 功能,它提供了一种方便的方式来启用由您管理的 Kubernetes 集群。

图 7.6 - 显示在 Docker Desktop 中启用 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),

通过这个更改,您现在可以键入以下内容(在的位置按下Tab键):

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 应用程序的屏幕截图

图 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/上有一个很棒的交互式教程。

注意

如果您有兴趣深入了解使用DockerKubernetes构建应用程序,以下链接是一个很好的起点(还有其他内容的进一步链接):

docs.docker.com/develop/

kubernetes.io/docs/home/

总结

在本章中,我们介绍了容器,并看到它们如何使应用程序及其依赖项打包在一起,以便在运行 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),在其中运行命令,并根据需要终止它们。

在本章中,我们将重新讨论发行版,这次从发行版管理的角度来看。特别是,我们将看看如何使用exportimport命令备份发行版或将其复制到另一台机器。我们还将看看如何快速创建一个基于 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 的屏幕截图

图 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 镜像页面的截图

图 8.2 - Docker Hub 上.NET 镜像页面的截图

正如您在这个截图中所看到的,有许多可用的.NET 镜像。在本章中,我们将使用.NET 5.0 镜像,特别是 SDK 镜像,因为我们希望能够测试构建应用程序(而不仅仅是运行为运行时镜像设计的应用程序)。

通过点击dotnet/sdk页面,我们可以找到我们需要使用的镜像标签来拉取和运行镜像:

图 8.3 - Docker Hub 上显示.NET 5.0 SDK 镜像标签的截图

图 8.3 - Docker Hub 上显示.NET 5.0 SDK 镜像标签的截图

正如这个截图所示,我们可以运行docker pull mcr.microsoft.com/dotnet/sdk:5.0将镜像拉取到我们的本地机器上。

现在我们已经找到了要用作新发行版起点的镜像,接下来有几个步骤来准备它以便与 WSL 一起使用。让我们看看这些步骤是什么。

配置一个准备用于 WSL 的容器

在我们可以导出刚从 Docker Hub 拉取的镜像之前,我们需要进行一些调整,以使其与 WSL 完全兼容:

  1. 首先,我们将从镜像创建一个正在运行的容器:
dotnet to make it easier to refer to it later. We also passed the -it switches to start the container with interactive accessnote the final line in the previous output showing that we're at a shell prompt inside the container.
  1. 首先要设置的是 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.
  1. 接下来,我们将添加/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

  1. 首先,让我们用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.
  1. 接下来,我们可以切换到新的 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语句来允许传递USERNAMEPASSWORD。之后,我们使用RUN运行了一系列命令来配置镜像。通常,在一个Dockerfile中,您会看到这些命令被连接为一个单独的RUN步骤,以帮助减少层数和大小,但在这里,我们只是要导出完整的文件系统,所以无所谓。让我们看一下这些命令:

  • 我们有useradd,之前我们用它来创建用户,这里我们使用它和USERNAME参数值。

  • passwd命令要求用户输入密码两次,所以我们使用echo在两次密码之间输出一个换行,并将其传递给passwd。我们调用bash来运行这个命令,这样我们就可以使用\n来转义换行符。

  • 我们再次使用echo来将/etc/wsl.conf的内容设置为配置 WSL 的默认用户。

  • 我们调用usermod将用户添加到sudoers 组,以允许用户运行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 的exportimport命令。这些命令允许您将发行版复制到其他计算机,或在重新安装计算机时备份和恢复发行版。它们还提供了一种克隆发行版的方法,如果您想要进行实验或在副本中工作而不影响原始发行版。

您还看到了如何使用容器构建新的发行版。这提供了一种有效的方式来设置新的发行版,以便在其中工作,或者快速测试应用程序而不影响原始发行版。如果您在项目之间具有不同的技术堆栈,并且希望在它们的依赖之间有一些隔离,那么这也可以是设置每个项目发行版的好方法。能够以脚本方式创建这些发行版有助于提高生产力,如果您发现自己使用这种多发行版方法。

随着我们通过使用 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 的截图

图 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 - 显示命令面板的截图

图 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 命令的屏幕截图

图 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 文件夹选择器的屏幕截图

图 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 - 显示推荐扩展提示的屏幕截图

图 9.5 - 显示推荐扩展提示的屏幕截图

此屏幕截图中的提示出现是因为您刚刚打开的文件夹包含一个列出 Python 扩展的.vscode/extensions.json文件。当提示出现时,要么单击Install All安装扩展,要么单击Show Recommendations在安装之前检查扩展。请注意,即使您之前在使用 Remote-WSL 之前已在 Visual Studio Code 中安装了 Python 扩展,您也可能会收到提示:

图 9.6 - 显示在 Windows 中安装了 Python 但未安装 WSL 的屏幕截图

图 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 版本选择器的屏幕截图

图 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 配置选择器的屏幕截图

图 9.8 - 显示 Python 配置选择器的屏幕截图

此屏幕截图显示了 Python 扩展允许您选择的一组常见调试选项。我们将在稍后看到如何配置它以实现完全灵活性,但现在选择Flask。这将使用 Flask 框架启动应用程序并附加调试器:

图 9.9 - 显示在调试器下运行应用程序的屏幕截图

图 9.9 - 显示在调试器下运行应用程序的屏幕截图

在上一个屏幕截图中,您可以看到已打开集成终端窗口,并且 Visual Studio Code 已启动了我们的 Flask 应用程序。当应用程序启动时,它会输出它正在侦听的 URL(在此示例中为http://127.0.0.1:5000)。将光标悬停在此链接上会提示您使用Ctrl + 单击打开链接。这样做将在默认浏览器中打开 URL:

图 9.10-浏览器中显示 Web 应用程序的屏幕截图

图 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 中显示运行视图的屏幕截图

图 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 应用程序的屏幕截图

图 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 终端命令的截图

图 9.13 - 展示新的 Windows 终端命令的截图

这个截图展示了命令面板中的新命令。打开命令使用 Windows 终端中的默认配置打开 Visual Studio Code 工作区文件夹。打开活动文件夹命令在默认配置中打开包含当前打开文件的文件夹。另外两个命令使用配置文件打开对应于前面的命令,但允许您选择使用哪个 Windows 终端配置文件打开路径。

除了从命令面板中访问的命令外,该扩展还为资源管理器视图中的文件和文件夹添加了右键菜单的新项目:

图 9.14 - 展示右键菜单命令的截图

图 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 编辑器的屏幕截图

图 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 的屏幕截图

图 9.16 - 显示从 WSL 运行的 gitk.exe 的屏幕截图

在这个屏幕截图中,您可以看到从 WSL Git 存储库运行的gitk Windows 应用程序,并通过文件系统映射访问内容。如果您有其他首选的用于查看 Git 历史记录的 Windows 应用程序,那么这种方法也可以工作,只要该应用程序在您的路径中。如果在运行这些命令时忘记添加.exe,您可能希望查看第五章Linux 到 Windows 的互操作性为 Windows 应用程序创建别名部分。

由于 Windows 应用程序通过\\wsl$共享使用 Windows 到 Linux 文件映射,您可能会注意到对于大型 Git 存储库,应用程序加载速度较慢,因为这种映射的开销较大。另一种方法是在 Visual Studio Code 中使用扩展,例如Git Graphhttps://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph):

图 9.17 - 显示在 Visual Studio Code 中的 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-WSLRemote-SSH一起。所有这些扩展都允许您将用户界面方面与代码交互分离,例如加载、运行和调试代码。通过 Remote-Containers,我们指示 Visual Studio Code 在我们在Dockerfile中定义的容器内运行这些代码交互(请参阅第七章在 WSL 中使用容器介绍 Dockerfiles部分)。

当 Visual Studio Code 在开发容器中加载我们的项目时,它经过以下步骤:

  1. 从 Dockerfile 构建容器镜像

  2. 使用生成的镜像运行容器,将源代码挂载到容器中。

  3. 在容器中为用户界面安装 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-显示开发容器配置列表的屏幕截图

图 10.1-显示开发容器配置列表的屏幕截图

如此屏幕截图所示,我们可以从一系列预定义的开发容器配置中选择。对于示例项目,请选择.devcontainer文件夹,并配置devcontainer.jsonDockerfile以使用 Python 3。添加这些文件后,您应该会看到以下提示:

图 10.2-显示重新在容器中打开提示的屏幕截图

图 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

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

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 浏览器中的网页

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 应用程序部分,只是这一次,我们将在我们的开发容器中进行操作:

  1. 在开发容器中设置 Docker。

  2. 构建应用程序 Docker 镜像。

  3. 运行应用程序容器。

让我们首先看看如何设置开发容器,以允许我们构建应用程序容器镜像。

在开发容器中设置 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 桌面集成):

  1. 挂载一个卷,将 WSL 中的~/.kube文件夹映射到开发容器中的/root/.kube,以共享连接到 Kubernetes API 的配置。

  2. 在开发容器的 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)。第二个命令使下载的二进制文件可执行。

现在我们已经配置好了kindkubectl的安装,我们需要对.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 集群创建的屏幕截图

图 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 中的应用的截图

图 10.7 - Windows 浏览器显示 Kubernetes 中的应用的截图

在这个截图中,我们可以看到 Windows 浏览器通过http://localhost:5000访问我们在 Kubernetes 中的应用。这是因为 Visual Studio Code 将 Windows 端口5000转发到开发容器内部的端口5000,这由kubectl port-forward处理,并转发到我们为应用部署的 Kubernetes 服务。

在本节中,我们使用了Visual Studio CodeRemote-ContainersDocker来创建一个容器化的开发环境,用于处理 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属性中指定了端口50005001。当 Visual Studio Code 启动开发容器时,它将自动开始转发这些端口,帮助我们平滑地进行工作流程。

要查看正在转发的端口,请切换到REMOTE EXPLORER视图(例如,通过运行Remote Explorer: Focus on Forwarded Ports View命令):

图 10.8 - 显示转发端口视图的屏幕截图

图 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 中的命令安装最新版本的工具,那么您团队中的不同成员在其开发容器中可能会有不同版本的工具,这取决于他们构建开发容器的时间以及那时工具的最新版本是什么。此外,您可能会添加一个新工具并重新构建开发容器,并获取其他工具的更新版本。通常,工具在版本之间保持合理的兼容性水平,但偶尔会在版本之间更改其行为。这可能导致奇怪的情况,其中开发容器工具对一个开发人员有效,但对另一个开发人员无效,或者工具在重新构建开发容器(例如,添加新工具)之前工作正常,但然后无意中获取了其他工具的新版本。这可能会干扰您的工作流程,我通常更喜欢将工具固定到特定版本(例如本章中的kindkubectl),然后在方便的时间或需要时明确更新它们的版本。

始终安装的扩展和 dotfiles

在设置开发容器时,您可以指定在创建开发容器时要安装的扩展。为此,您可以将以下内容添加到devcontainer.json中:

"extensions": [
    "redhat.vscode-yaml",
    "ms-vsliveshare.vsliveshare"
],

在这里,您可以在 JSON 中看到extensions属性,它指定了一个扩展 ID 的数组。要找到扩展的 ID,请在 Visual Studio Code 的EXTENSIONS视图中搜索扩展并打开它。您将看到以下详细信息:

图 10.9 - 在 Visual Studio Code 中显示扩展信息的屏幕截图

图 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 上度过的,我仍然发现我更容易输入clsmd这样的命令,而不是它们的等效命令clearmkdir。使用 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 一样,azkubectl都提供了在azkubectl中获取数据的选项,这些部分涵盖的技术可能与您正在使用的其他 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 commitgit config

Bash 自动补全不仅仅是完成命令名称;你可以使用git checkout my<TAB>来完成分支名称为git checkout my-branch

一旦你习惯了 bash 自动补全,你会发现它可以大大提高生产力!

接下来,让我们看看与远程 Git 仓库进行身份验证的选项。

使用 Git 进行身份验证

通过Secure ShellSSH)密钥进行 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。要配置,请从您的distributiondistro)运行以下命令:

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 存储库

图 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 扩展程序的屏幕截图

图 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 的屏幕截图

图 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 提示的屏幕截图

图 11.4 – 显示 Powerline 提示的屏幕截图

正如这个屏幕截图所示,Powerline 使用了一些特殊的字体字符,并非所有字体都设置了这些字符,所以第一步是确保我们有一个合适的字体。Windows 终端附带了一个名为 CascadiaCodePL.ttfCascadiaMonoPL.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/releasespowerline-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 的截图

图 11.5 - 展示 jq playground 的截图

这个截图显示了在jq playground 中打开的前面的例子。在左上角,你可以看到过滤器(.parts[].name),在下面是输入的 JSON,右边是jq的输出。当你在处理复杂的查询时,playground 可以是一个有用的环境,底部的命令行部分甚至会给出你可以复制并在脚本中使用的命令行。

现在你已经知道jq可以做什么了,让我们从一个简单的查询开始。我们要处理的 JSON 有两个顶级属性:titleparts。如果我们想提取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数组被传递到一个过滤器中,该过滤器为每个数组项创建一个带有namechapters属性的对象。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-ObjectForEach-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 }。这将创建一个具有两个属性的对象:NameChapterCountchapters属性是一个 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 对象,这次使用NameChapters属性。要创建Chapters属性,我们只需获取每个章节的名称,我们可以像在本节前面选择部分名称时一样使用Select-Object命令,但这次我们将其用在ForEach-Object片段内。能够以这种方式组合命令使我们具有很大的灵活性。

在之前的例子中,我们一直在处理使用Get-Content从本地文件加载的数据。要从 URL 下载数据,PowerShell 提供了一些方便的命令:Invoke-WebRequestInvoke-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 应用程序占位符站点的屏幕截图

图 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']是过滤数组的语法,在这里我们将其过滤为仅返回包含值为FTPpublishMethod属性的项目。

  • 前面查询的输出仍然是一个项目数组,所以我们使用|[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 查询中的最终选择器。这样做的结果是生成一个包含publishUrluserNameuserPWD属性值的数组。

这些属性数组以制表符分隔的值输出,并且通过将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 应用程序

图 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 输出。请注意statusloadBalancer属性的空值。如果我们等待一会儿然后重新运行命令,我们会看到以下输出:

    "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 进行查询

让我们看看如何编写一个查询,查找具有typePodScheduledstatus设置为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"
    }
  ]
}{
  ...
}

在这里,我们包含了所有的条件,但由于我们只想要那些尚未被调度的条件,我们只想包括特定的条件。为此,我们可以使用jqselect过滤器,它处理一个值数组,并通过那些匹配指定条件的值。在这里,我们将使用它来过滤状态条件,只包括那些type设置为PodScheduledstatus设置为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 功能。有了这个背景,您还看到了一些使用azkubectl进行部署的 JSON 工作示例。除了涵盖每个 CLI 可能面临的场景外,示例还演示了可以应用于提供 JSON 数据的其他 CLI(或 API)的技术。能够有效地处理 JSON 数据为您提供了强大的能力,可以在脚本中使用,节省您的时间。

这是本书的最后一章,我希望我已经成功地传达了我对 WSL 2 以及它带来的可能性的一些兴奋。在 Windows 上享受 Linux 吧!

posted @   绝不原创的飞龙  阅读(379)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示