Kubernetes-Windows-实用指南-全-

Kubernetes Windows 实用指南(全)

原文:zh.annas-archive.org/md5/D85F9AD23476328708B2964790249673

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

从版本 1.14 开始,Kubernetes 带来了 2019 年最受期待的功能:对 Windows Server 容器工作负载的生产级支持。这是一个巨大的里程碑,使得所有严重依赖 Windows 技术的企业能够迁移到云原生技术。开发人员和系统运营商现在可以利用相同的工具和流水线来部署 Windows 和 Linux 工作负载,以类似的方式扩展它们,并进行高效的监控。从商业角度来看,Windows 容器的采用意味着比普通虚拟机更低的运营成本和更好的硬件利用率。

你手中拿着一本书,将指导你如何在 Microsoft Windows 生态系统中使用 Kubernetes 和 Docker 容器 - 它涵盖了混合 Windows/Linux Kubernetes 集群部署,并使用 Windows 客户端机器处理集群操作。由于 Windows 在 Kubernetes 中的支持是一个相当新的概念,你可以期待官方文档和指南仍然很少。在这本书中,我们的目标是系统化你关于涉及 Windows 的 Kubernetes 场景的知识。我们的目标是创建 Kubernetes 在 Windows 上的终极指南。

这本书适合谁

这本书的主要受众是需要将 Windows 容器工作负载整合到他们的 Kubernetes 集群中的 Kubernetes DevOps 架构师和工程师。如果你是 Windows 应用程序(特别是.NET Framework)开发人员,而且你还没有使用过 Kubernetes,这本书也适合你!除了关于部署混合 Windows/Linux Kubernetes 集群的策略,我们还涵盖了 Kubernetes 背后的基本概念以及它们如何映射到 Windows 环境。如果你有兴趣将现有的.NET Framework 应用程序迁移到在 Kubernetes 上运行的 Windows Server 容器,你肯定会找到如何解决这个问题的指导。

这本书涵盖了什么

第一章,创建容器,描述了目前在 Linux 和 Windows 操作系统中使用的不同容器类型。本章的主要目标是演示如何构建一个示例 Windows 容器,运行它,并执行基本操作。

第二章《在容器中管理状态》讨论了管理和持久化容器化应用程序状态的可能方法,并解释了如何在容器上挂载本地和云存储卷(Azure Files SMB 共享),以便在 Windows 容器上运行像 MongoDB 这样的集群数据库引擎。

第三章《使用容器镜像》专注于容器镜像,这是分发容器化应用程序的标准方式。本章的目标是演示如何使用 Docker Hub 和 Azure 容器注册表,以及如何在部署流水线中安全地交付容器镜像。

第四章《Kubernetes 概念和 Windows 支持》使您熟悉了核心 Kubernetes 服务,如 kubelet、kube-proxy 和 kube-apiserver,以及最常用的 Kubernetes 对象,如 Pod、Service、Deployment 和 DaemonSet。您将了解为什么 Kubernetes 中的 Windows 支持很重要,以及当前关于 Windows 容器和 Windows 节点的限制是什么。我们还将重点放在为不同用例创建简单的开发 Kubernetes 集群上。

第五章《Kubernetes 网络》描述了 Kubernetes 的网络模型和可用的 Pod 网络解决方案。您将学习如何为具有 Windows 节点的 Kubernetes 集群选择最合适的网络模式。

第六章《与 Kubernetes 集群交互》展示了如何从 Windows 机器使用 kubectl 与 Kubernetes 集群进行交互和访问。例如,我们将展示如何与本地开发集群一起工作,以及最常见和有用的 kubectl 命令是什么。

第七章《部署混合本地 Kubernetes 集群》演示了如何处理虚拟机的供应和部署混合的 Windows/Linux Kubernetes 集群,其中包括 Linux 主节点/节点和 Windows 节点。本地部署是最通用的部署类型,因为它可以使用任何云服务提供商或私人数据中心进行。

第八章《部署混合 Azure Kubernetes 服务引擎集群》概述了如何使用 AKS Engine 部署混合 Windows/Linux Kubernetes 集群的方法,并演示了一个示例部署一个 Microsoft IIS 应用程序。

第九章《部署您的第一个应用程序》演示了如何将一个简单的 Web 应用程序以命令式和声明式的方式部署到 Kubernetes,并讨论了管理在 Kubernetes 中运行的应用程序的推荐方式。我们还将介绍如何专门在 Windows 节点上调度 Pod,并如何扩展在 Kubernetes 上运行的 Windows 应用程序。

第十章《部署 Microsoft SQL Server 2019 和 ASP.NET MVC 应用程序》描述了如何将在 Windows 容器中运行的 ASP.NET MVC 实现的样本投票应用程序部署到 AKS Engine 集群,以及 Microsoft SQL Server 2019(在 Linux 容器中运行)。您还将了解如何使用 Visual Studio 远程调试器调试在 Kubernetes 中运行的.NET 应用程序。

第十一章《配置应用程序使用 Kubernetes 功能》描述了如何实现和配置 Kubernetes 的更高级功能,包括命名空间、ConfigMaps 和 Secrets、持久存储、健康和就绪检查、自动缩放和滚动部署。本章还展示了 Kubernetes 中的基于角色的访问控制(RBAC)的工作原理。

第十二章《Kubernetes 的开发工作流程》展示了如何将 Kubernetes 作为微服务开发的平台。您将学习如何使用 Helm 打包应用程序,以及如何使用 Azure Dev Spaces 改进开发体验。此外,本章还描述了如何在 Kubernetes 中运行的容器化应用程序中使用 Azure 应用程序洞察和快照调试器。

第十三章《保护 Kubernetes 集群和应用程序》涵盖了 Kubernetes 集群和容器化应用程序的安全性。我们将讨论 Kubernetes 的一般推荐安全实践以及 Windows 特定的考虑因素。

第十四章《使用 Prometheus 监控 Kubernetes 应用程序》着重于如何监控 Kubernetes 集群,特别是运行在 Windows 节点上的.NET 应用程序。您将学习如何使用 Prometheus Helm 图表部署完整的监控解决方案,以及如何配置它来监控您的应用程序。

第十五章《灾难恢复》讨论了备份 Kubernetes 集群和灾难恢复策略。主要重点是展示哪些组件需要备份以安全恢复集群,以及如何自动化这个过程。

第十六章《运行 Kubernetes 的生产考虑因素》是针对在生产环境中运行 Kubernetes 的一些建议性建议。

为了充分利用本书

建议具有一些关于 Docker 和 Kubernetes 的一般知识,但不是必需的。我们将在专门的章节中涵盖 Windows 上容器化和 Kubernetes 本身的基本概念。对于那些专注于将 Windows 应用程序部署到 Kubernetes 的章节,建议您具有.NET Framework,C#和 ASP.NET MVC 的基本经验。请注意,本书中每个指南和示例都有官方 GitHub 存储库中的对应物:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows

在整本书中,您将需要自己的 Azure 订阅。您可以在这里阅读有关如何获取个人使用的有限免费帐户的更多信息:azure.microsoft.com/en-us/free/

本书涵盖的软件/硬件 操作系统要求
Visual Studio Code,Docker Desktop,Kubectl,带 16 GB RAM 的 Azure CLI Windows 10 Pro,企业版或教育版(1903 版或更高版本;64 位),Windows Server 2019,Ubuntu Server 18.04

如果您使用本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库访问代码(链接在下一节中提供)。这样做将有助于避免与复制和粘贴代码相关的任何潜在错误。

下载示例代码文件

您可以从您在www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support注册,将文件直接发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. www.packt.com登录或注册。

  2. 选择“支持”选项卡。

  3. 单击“代码下载”。

  4. 在“搜索”框中输入书名,然后按照屏幕上的说明操作。

一旦文件下载完成,请确保使用最新版本的解压缩或提取文件夹:

  • WinRAR/7-Zip 适用于 Windows

  • Zipeg/iZip/UnRarX 适用于 Mac

  • 7-Zip/PeaZip 适用于 Linux

该书的代码捆绑包也托管在 GitHub 上,网址为github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还提供了来自我们丰富书籍和视频目录的其他代码捆绑包,可在**github.com/PacktPublishing/**上找到。去看看吧!

下载彩色图片

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图片。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781838821562_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这是一个例子:“将下载的WebStorm-10*.dmg磁盘映像文件挂载为系统中的另一个磁盘。”

代码块设置如下:

html, body, #map {
 height: 100%; 
 margin: 0;
 padding: 0
}

当我们希望引起您对代码块的特定部分的注意时,相关行或项目以粗体显示:

[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
exten => s,102,Voicemail(b100)
exten => i,1,Voicemail(s0)

任何命令行输入或输出都以以下方式编写:

$ mkdir css
$ cd css

粗体:表示一个新术语、一个重要词或屏幕上看到的词。例如,菜单或对话框中的单词会在文本中以这种方式出现。这是一个例子:“从管理面板中选择系统信息。”

警告或重要说明会以这种方式出现。提示和技巧会以这种方式出现。

第一部分:创建和使用容器

本节的目标是介绍不同的容器化技术以及选择一种变体而不是另一种的好处。您将了解如何在 Windows 上对应用程序进行容器化,并了解创建和维护图像涉及的关键步骤。

本节包括以下章节:

  • 第一章,创建容器

  • 第二章,在容器中管理状态

  • 第三章,使用容器图像

第一章:创建容器

容器操作系统级虚拟化的概念源自 Unix V7 操作系统(OS)中的chroot系统调用,可以追溯到 20 世纪 70 年代末。从最初的进程隔离和chroot 监狱的简单概念开始,容器化经历了快速发展,并在 2010 年代成为主流技术,随着Linux 容器LXC)和 Docker 的出现。2014 年,微软宣布在即将发布的 Windows Server 2016 中支持 Docker Engine。这是 Windows 容器和 Windows 上的 Kubernetes 故事的开始。

在本章中,我们将通过突出 Windows 操作系统上容器化与 Linux 上的重要区别以及 Windows 上的容器运行时类型,即 Windows Server 容器(或进程隔离)和 Hyper-V 隔离,为您提供更好的理解。我们还将学习如何为 Windows 10 安装 Docker Desktop 以进行开发,并在您的计算机上运行我们的第一个示例容器。

本章将涵盖以下主题:

  • Linux 与 Windows 容器

  • 了解 Windows 容器变体

  • 安装 Windows 工具的 Docker Desktop

  • 构建您的第一个容器

技术要求

本章的要求如下:

  • 在 BIOS 中启用Intel 虚拟化技术Intel VT)或AMD 虚拟化AMD-V)技术功能

  • 至少 4GB 的 RAM

  • 已安装 Windows 10 Pro、企业版或教育版(1903 版本或更高版本,64 位)

  • Visual Studio Code

有关在 Windows 上运行 Docker 和容器的硬件要求的更多信息,请参阅docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/system-requirements

支持从周年更新(版本 1607,构建 14393)开始的 Windows 10 版本,但建议使用版本 1903 以获得最佳体验,因为它具备所有必要的功能。有关 Windows 10 版本和容器运行时兼容性的更多详细信息,请参阅docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility

可以免费从官方网页下载 Visual Studio Code:code.visualstudio.com/

您可以从本书的官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter01

Linux 与 Windows 容器

Linux 和 Windows 上的容器化都旨在实现相同的目标 - 创建可预测且轻量的环境,与其他应用程序隔离。对于 Linux,容器使用的一个经典示例可以是运行使用 Flask 编写的 Python RESTful API,而不必担心与其他应用程序所需的 Python 模块之间的冲突。同样,对于 Windows,容器可以用于托管完全与同一台机器上运行的其他工作负载隔离的 Internet Information Services (IIS) web 服务器。

与传统的硬件虚拟化相比,容器化的代价是与主机操作系统紧密耦合,因为它使用相同的内核来提供多个隔离的用户空间。这意味着在 Linux 操作系统上运行 Windows 容器,或者在 Windows 操作系统上运行 Linux 容器,不可能在没有传统硬件虚拟化技术的额外帮助下本地实现。

在本书中,我们将专注于 Docker 容器平台,这是在 Windows 上运行容器所必需的。现在,让我们总结 Docker Engine 提供的 Linux 和 Windows 上容器化支持的当前状态,以及在开发和生产场景中可能的解决方案。

Linux 上的 Docker 容器化

最初,Docker Engine 主要是为 Linux 操作系统开发的,它为 Docker 运行时提供了以下内核特性:

  • 内核命名空间:这是容器的核心概念,它使得创建隔离的进程工作空间成为可能。命名空间分割内核资源(比如网络堆栈、挂载点等),这样每个进程工作空间可以访问自己的一组资源,并确保它们不能被其他工作空间的进程访问。这就是确保容器隔离的方式。

  • 控制组:资源使用限制和隔离是容器化的次要核心概念。在 Linux 上,这个特性由cgroups提供,它使得资源限制(CPU 使用率、RAM 使用率等)和优先访问资源对于一个进程或一组进程来说成为可能。

  • 分层文件系统功能:在 Linux 上,UnionFS联合挂载的许多实现之一——这是一个文件系统服务,允许来自不同文件系统的文件和目录被统一到一个透明、一致的文件系统中。这个特性对于由不可变层组成的 Docker 容器镜像至关重要。在容器运行时,只读层会被透明地叠加在一起,与可写的容器层一起。

Docker Engine 负责为容器提供基本运行时,抽象容器管理,并使用 REST API 向客户端层暴露功能,比如 Docker CLI。Docker 在 Linux 上的架构可以用以下图表总结:

从 Linux 操作系统的角度来看,容器运行时架构如下图所示。这个架构适用于 Linux 上的容器引擎,不仅仅是 Docker。

接下来,我们将看一下 Windows 上的 Docker 容器化。

在 Windows 上的 Docker 容器化

2014 年,当微软宣布在即将发布的 Windows Server 2016 中支持 Docker Engine 时,Docker 容器引擎在 Linux 上已经成熟,并被证明是容器管理的行业标准。这个事实推动了 Docker 和 Windows 容器化支持的设计决策,最终为运行进程隔离的 Windows Server 容器提供了类似的架构。Docker Engine 使用的 Windows 内核功能大致映射如下:

  • 内核命名空间:这个功能是由 Windows 内核中的对象命名空间和进程表等提供的。

  • 控制组:Windows 有自己的作业对象概念,允许一组进程作为单个单元进行管理。基本上,这个功能提供了类似于 Linux 上的cgroups的功能。

  • 层文件系统功能:Windows 容器隔离文件系统是一个文件系统驱动程序,为在 Windows 容器中执行的进程提供虚拟文件系统视图。这类似于 Linux 操作系统上的UnionFS或其他联合挂载的实现。

在这些低级功能之上,服务层由一个主机计算服务HCS)和一个主机网络服务HNS)组成,为使用 C#和 Go(hcsshim)提供了运行和管理容器的公共接口。有关当前容器平台工具的更多信息,请参阅官方文档:docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/containerd#hcs

重要的是要知道,Windows 容器有两种类型:进程隔离和 Hyper-V 隔离。它们之间的区别将在下一节中解释 - 隔离是容器的运行时属性,您可以期望它们在一般情况下表现类似,并且只在安全性和兼容性方面有所不同。

以下图表总结了容器化架构和 Docker 对 Windows 的支持:

为了与 Linux 上容器化的高级架构进行比较,以下图表展示了 Windows 的多容器运行时架构。在这一点上,我们只考虑进程隔离的 Windows Server 容器,它们与 Linux 上的容器非常相似,但在下一节中,我们还将介绍 Windows 上容器的Hyper-V 隔离架构:

接下来,让我们看一下 Linux 和 Windows 上容器的一些区别。

Linux 和 Windows 上容器之间的关键区别

Linux 和 Windows 上的 Docker 容器在原则上旨在解决相同的问题,目前,容器管理体验开始在这些平台上趋于一致。然而,如果您来自 Linux 生态系统,并且在那里广泛使用了 Docker,您可能会对一些不同感到惊讶。让我们简要总结一下。

最大且最明显的限制是 Windows 主机操作系统和 Windows 容器操作系统的兼容性要求。在 Linux 的情况下,您可以安全地假设,如果主机操作系统内核运行的是最低要求版本 3.10,那么任何 Linux 容器都将无需任何问题地运行,无论它基于哪个发行版。对于 Windows 来说,可以运行具有与受支持的主机操作系统版本完全相同的基础操作系统版本的容器,而不受任何限制。在旧的主机操作系统上运行更新的容器操作系统版本是不受支持的,而且更重要的是,在更新的主机操作系统上运行旧的容器操作系统版本需要使用Hyper-V 隔离。例如,运行 Windows Server 版本 1803 构建 17134 的主机可以原生地使用具有基础镜像版本 Windows Server 版本 1803 构建 17134 的容器,但在需要使用 Hyper-V 隔离的情况下,运行具有 Windows Server 版本 1709 构建 16299 的容器,并且根本无法启动具有 Windows Server 2019 构建 17763 的容器。以下表格可视化了这一原则:

主机操作系统版本 容器基础镜像操作系统版本 兼容性
Windows Server,版本 1803 构建 17134 Windows Server,版本 1803 构建 17134 进程Hyper-V隔离
Windows Server,版本 1803 构建 17134 Windows Server,版本 1709 构建 16299 Hyper-V隔离
Windows Server,版本 1803 构建 17134 Windows Server 2019 构建 17763 不支持
Windows Server 2019 构建 17763 Windows Server 2019 构建 17763 进程Hyper-V隔离

有关更详细的兼容性矩阵,请参阅官方微软文档:docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility#choose-which-container-os-version-to-use

值得一提的是,Hyper-V 隔离的要求可能是云环境或在虚拟机上运行 Docker 时的一个重要限制。在这种情况下,Hyper-V 隔离需要由 hypervisor 启用嵌套虚拟化功能。我们将在下一节详细介绍 Hyper-V 隔离。

在 Linux 和 Windows 容器的基本图像之间的大小差异是你可能注意到的另一个重要方面。目前,最小的 Windows Server 图像mcr.microsoft.com/windows/nanoserver:1809大小为 98 MB,而例如,Alpine Linux 的最小图像alpine:3.7只有 5 MB。完整的 Windows Server 图像mcr.microsoft.com/windows/servercore:ltsc2019超过 1.5 GB,而 Windows 的基本图像mcr.microsoft.com/windows:1809为 3.5 GB。但值得一提的是,自 Windows Server 2016 Core 图像首次发布时,图像大小为 6 GB,这些数字不断下降。

这些差异更多地可以看作是 Windows 上 Docker 容器的限制。然而,有一个方面是 Windows 比 Linux 提供更多灵活性的地方 - 支持在 Windows 上运行 Linux 容器。Windows 10 的 Docker Desktop 支持这样的场景。尽管这个功能仍在开发中,但在 Windows 10 上使用 Hyper-V 隔离可以同时托管 Linux 容器和 Windows 容器。我们将在下一节更详细地介绍这个功能。而在 Linux 上运行 Windows 容器的相反情况没有本地解决方案,需要在 Linux 主机上手动托管额外的 Windows 虚拟机。

Windows Server 也支持运行 Linux 容器,前提是启用了Linux 容器在 Windows 上LCOW)实验性功能。

在下一节中,我们将重点关注不同 Windows 容器运行时变体之间的差异。

理解 Windows 容器的变体

Windows 容器有两种不同的隔离级别:进程和 Hyper-V。进程隔离也被称为Windows Server 容器WSC)。最初,进程隔离仅适用于 Windows Server 操作系统,而在 Windows 桌面版本上,您可以使用 Hyper-V 隔离运行容器。从 Windows 10 的 1809 版本(2018 年 10 月更新)和 Docker Engine 18.09.1 开始,进程隔离也适用于 Windows 10。

在官方文档中,您可能会发现 Windows 容器类型运行时这些术语。它们也指的是隔离级别,这些术语可以互换使用。

现在,让我们来看看这些隔离级别的区别,它们的用例是什么,以及如何通过指定所需的隔离类型来创建容器。

进程隔离

进程隔离容器,也称为WSC,是 Windows Server 上容器的默认隔离模式。进程隔离的架构类似于在 Linux OS 上运行容器时的架构:

  • 容器使用相同的共享内核。

  • 隔离是在内核级别提供的,使用诸如进程表、对象命名空间和作业对象等功能。更多信息可以在Windows 上的 Docker 容器化部分找到。

这在以下图表中总结如下:

进程隔离为容器提供了轻量级的运行时(与 Hyper-V 隔离相比),并提供了更高的部署密度、更好的性能和更低的启动时间。然而,在使用这种类型的隔离时,有一些要考虑的要点:

  • Docker 容器基础镜像必须与容器主机操作系统的版本匹配。例如,如果您正在运行 Windows 10,1903 版本,您只能运行使用 Windows 10 或 Windows Server 1903 版本基础镜像的容器。这意味着您必须为每个发布的 Windows 版本重新构建镜像(仅适用于主要功能更新)。

  • 这应该只用于执行受信任的代码。为了执行不受信任的代码,建议使用 Hyper-V 隔离。

使用 Windows 10,1809 版本及更高版本,可以在容器运行时使用进程隔离,前提是您正在运行 Docker Desktop for Windows 2.0.1.0 (Edge发布渠道)或更高版本和 Docker Engine 18.09.1+。对于 Windows 10,容器的默认隔离级别是 Hyper-V,为了使用进程隔离,必须在使用--isolation=process参数创建容器时明确指定:

docker run -d --isolation=process mcr.microsoft.com/windows/nanoserver:1903 cmd /c ping localhost -n 100

此选项也可以作为参数指定给 Docker 守护程序,使用--exec-opt参数。有关更多详细信息,请参阅官方 Docker 文档:docs.docker.com/engine/reference/commandline/run/#specify-isolation-technology-for-container---isolation

在 Windows 10 操作系统上使用进程隔离容器仅建议用于开发目的。对于生产部署,您仍应考虑使用 Windows Server 进行进程隔离容器。

Hyper-V 隔离

Hyper-V 隔离是 Windows 容器的第二种隔离类型。在这种隔离类型中,每个容器都在一个专用的、最小的 Hyper-V 虚拟机中运行,可以简要总结如下:

  • 容器不与主机操作系统共享内核。每个容器都有自己的 Windows 内核。

  • 隔离是在虚拟机 hypervisor 级别提供的(需要安装 Hyper-V 角色)。

  • 主机操作系统版本和容器基础操作系统版本之间没有兼容性限制。

  • 这是推荐用于执行不受信任的代码和多租户部署,因为它提供了更好的安全性和隔离性。

Hyper-V 隔离的详细架构可以在以下图表中看到:

在选择隔离级别时,这种隔离类型会带来一些成本:

  • 与进程隔离相比,Hyper-V 隔离涉及虚拟化开销、更高的内存和 CPU 使用量,但仍然比在 Windows Nano Server 上运行完整虚拟机提供更好的性能。您可以在以下表格中查看使用不同隔离级别运行容器的内存要求。

  • 与进程隔离相比,容器的启动时间较慢。

  • 在虚拟机上运行容器时需要嵌套虚拟化。这可能是一些虚拟化程序和云部署的限制。以下表格显示了 Windows Server 1709 容器的内存要求:

容器基础镜像 进程隔离(WSC) Hyper-V 隔离
Nano Server 30 MB 110 MB + 1 GB 页面文件
Server Core 45 MB 360 MB + 1 GB 页面文件

与进程隔离相比,容器镜像保持不变;在创建实际容器时,只需要指定不同的隔离级别。您可以使用--isolation=hyperv参数来实现这一点:

docker run -d --isolation=hyperv mcr.microsoft.com/windows/nanoserver:1809 cmd /c ping localhost -n 100

请注意,在这种情况下,即使您使用的是 Windows 10 的 1903 版本,也可以使用 1809 版的容器基础镜像而没有任何限制。

在 Windows 10 上运行容器时,Hyper-V 隔离是默认的隔离级别,因此不需要--isolation=hyperv参数。反之亦然;进程隔离是 Windows Server 的默认级别,如果要使用 Hyper-V 隔离,必须明确指定。可以通过在daemon.json配置文件中指定exec-opts中的isolation参数来更改默认隔离级别。有关更多信息,请参阅docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-filedocs.docker.com/engine/reference/commandline/dockerd/#docker-runtime-execution-options

Windows 上的 Linux 容器

2017 年 4 月,Docker 宣布推出 LinuxKit,这是一个在不带 Linux 内核的平台上运行 Linux 容器的解决方案,即 Windows 和 macOS。LinuxKit 是一个用于构建便携和轻量级 Linux 子系统的工具包,其中只包含在特定平台上运行 Linux 容器所需的最低限度。尽管自 2016 年首次发布以来,Docker 能够在 Windows 上以有限的程度运行 Linux 容器,但 LinuxKit 的宣布是开始今天我们所知的Windows 上的 Linux 容器LCOW)故事的里程碑。

在生产部署中,不建议在 Windows 上运行 Linux 容器。使用 LinuxKit 和 MobyLinuxVM 仅适用于 Windows 桌面和开发目的。与此同时,LCOW 功能仍处于实验阶段,不适合生产环境使用。

LinuxKit 和 MobyLinuxVM

Docker for Windows(当时 Docker Desktop for Windows 的初始名称)最终配备了基于 LinuxKit 的专用 Hyper-V 虚拟机,名为 MobyLinuxVM。这个虚拟机的目的是为 Linux 容器提供一个最小的运行时,从技术上讲可以与 Windows 容器并存。

默认情况下,Docker Desktop for Windows 以 Linux 容器模式运行,使用 MobyLinuxVM。要切换到 Windows 容器模式,必须转到 Docker Desktop 托盘图标,选择切换到 Windows 容器.... Docker 将重新启动并切换到本机 Windows 容器。

在这个解决方案中,MobyLinuxVM 运行自己的 Docker 守护程序,技术上充当一个封装在虚拟机内部的独立容器主机。同样,Windows 有自己的 Docker 守护程序,负责 Windows 容器,并提供 Docker 客户端(CLI),可以与两个 Docker 守护程序通信。这个架构可以在下图中看到:

现在,让我们来看一个更为现代的在 Windows 上运行 Linux 容器的方法:LinuxKit LCOW。

LinuxKit LCOW 和 Hyper-V 隔离

与 MobyLinuxVM 方法相反,Windows 上的 Linux 容器LCOW)使用 Hyper-V 隔离容器来实现类似的结果。LCOW 适用于 Windows 10,配备 Docker for Windows 17.10,并适用于 Windows Server 1709 版本,配备 Docker 企业版的预览版本。

与 MobyLinuxVM 相比的主要区别是可以使用相同的 Docker 守护程序本地运行 Linux 和 Windows 容器。这个解决方案是支持在 Windows 上运行 Linux 容器的当前策略,但作为长期解决方案,在 2019 年 6 月,Docker 和微软开始合作,将 Windows 子系统版本 2 集成为 Windows 上的主要 Linux 容器运行时。最终,LinuxKit LCOW 和带有 Docker Desktop for Windows 的 MobyLinuxVM 将被淘汰。

下图显示了 LCOW:

要在 Docker Desktop(18.02 版本或更高版本)中启用 LCOW 支持,必须在 Docker 设置>*守护程序中启用实验性功能选项。创建 LCOW 容器需要指定--platform linux参数(如果平台选择是明确的,即镜像只存在于 Linux 中,则在较新版本的 Docker Desktop 中可以省略):

docker run -it --platform linux busybox /bin/sh

上述命令将创建一个 busybox Linux 容器,并进入交互式 Bourne shell(sh)。

截至 Docker Desktop for Windows 2.0.4.0 版本,启用 LCOW 功能后,无法运行 Docker 提供的开发 Kubernetes 集群(“一应俱全”)。

在这一部分,您了解了容器目前在 Windows 平台上的支持情况以及所提供运行时之间的关键区别。现在,我们可以开始安装Windows 的 Docker 桌面

安装 Windows 的 Docker 桌面工具

在 Windows 上创建 Kubernetes 应用程序需要一个用于开发和测试 Docker 容器的环境。在本节中,您将学习如何安装 Windows 的 Docker 桌面,这是开发、构建、交付和在 Windows 10 上运行 Linux 和 Windows 容器的推荐工具环境。首先,让我们在继续安装过程之前回顾一下先决条件和 Docker 的最低要求:

  • 至少 4GB 的 RAM。

  • 在 BIOS 中启用Intel 虚拟化技术 (Intel VT)或AMD 虚拟化 (AMD-V)技术。请注意,如果您将 VM 用作开发机器,Windows 的 Docker 桌面不保证支持嵌套虚拟化。如果您想了解更多关于这种情况的信息,请参考docs.docker.com/docker-for-windows/troubleshoot/#running-docker-desktop-for-windows-in-nested-virtualization-scenarios

  • 已安装 Windows 10 Pro、企业版或教育版(1903 版本或更高版本,64 位)。当前的 Docker 桌面支持 1703 版本或更高版本,但为了在本书的示例中获得最佳体验,建议您将其升级到 1903 版本或更高版本。您可以通过打开开始菜单,选择设置图标,然后导航到系统 > 关于来检查 Windows 的版本。您将在 Windows 规格下找到必要的详细信息。

Windows 的 Docker 桌面也被称为 Windows 的 Docker 和 Docker 社区版 (CE)。如果您正在遵循较旧的安装指南,这一点尤为重要。

如果您对 Windows Server 上的 Docker 企业版的安装感兴趣,请参考第七章,部署混合本地 Kubernetes 集群

稳定和边缘渠道

根据您的需求,您可以选择 Windows 的 Docker 桌面的两个发布渠道:稳定边缘。如果您满意以下情况,您应该考虑使用稳定渠道:

  • 您希望使用推荐和可靠的平台来处理容器。稳定频道中的发布遵循 Docker 平台稳定发布的发布周期。您可以期望稳定频道的发布每季度进行一次。

  • 您想选择是否发送使用统计信息。

如果您同意以下内容,可以考虑使用边缘频道:

  • 您希望尽快获得实验性功能。这可能会带来一些不稳定性和错误。您可以期望边缘频道的发布每月进行一次。

  • 您同意收集使用统计数据。

现在,让我们继续进行安装。

安装

本节中描述的安装过程遵循官方 Docker 文档的建议。让我们开始:

如果您在 Windows 系统上使用 chocolatey 来管理应用程序包,也可以使用官方的 Docker Desktop 可信包,网址为:chocolatey.org/packages/docker-desktop.

  1. 为了下载 Windows 版 Docker Desktop,请转到hub.docker.com/editions/community/docker-ce-desktop-windows。下载需要注册服务。您还可以选择直接链接来下载稳定频道发布(download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe)或边缘频道发布(download.docker.com/win/edge/Docker%20Desktop%20Installer.exe)。

如果需要,Docker Desktop for Windows 将自动启用 Hyper-V 角色并重新启动计算机。如果您是 VirtualBox 用户或 Docker Toolbox 用户,则将无法同时运行 VirtualBox VM,因为 Type-1 和 Type-2 hypervisors 不能同时运行。您仍然可以访问现有的 VM 映像,但无法启动 VM。

  1. 转到安装程序下载的目录,然后双击它。

  2. 通过选择“使用 Windows 容器而不是 Linux 容器”选项,默认启用 Windows 容器支持:

  1. 进行安装:

  1. 如果安装程序启用了 Hyper-V 角色,可能会提示您重新启动计算机。

  2. 启动 Docker 桌面应用程序。

  3. 等待 Docker 完全初始化。您将看到以下提示:

安装后,我们需要验证 Docker 是否已正确安装并能运行一个简单的hello world容器镜像。

验证安装

现在,让我们验证安装是否成功:

  1. 通过打开 Powershell 并执行以下命令来确认 Docker 客户端是否正常工作:
docker version
  1. 您应该看到类似以下的输出:
Client: Docker Engine - Community
 Version: 18.09.2
 API version: 1.39
 Go version: go1.10.8
 Git commit: 6247962
 Built: Sun Feb 10 04:12:31 2019
 OS/Arch: windows/amd64
 Experimental: false

Server: Docker Engine - Community
 Engine:
 Version: 18.09.2
 API version: 1.39 (minimum version 1.12)
 Go version: go1.10.6
 Git commit: 6247962
 Built: Sun Feb 10 04:13:06 2019
 OS/Arch: linux/amd64
 Experimental: false
  1. 运行基于官方 Powershell 镜像的简单容器:
docker run -it --rm mcr.microsoft.com/powershell pwsh -c 'Write-Host "Hello, World!"'
  1. 在运行此命令的第一次运行期间,将下载缺少的容器镜像层。过一段时间后,您将在 Powershell 的控制台输出中看到 Hello, World!:

  1. 恭喜!您已成功安装了 Windows 版 Docker 桌面并运行了您的第一个容器。

在下一小节中,您将学习如何为容器启用进程隔离。

运行进程隔离的容器

在 Windows 10 上,为了运行进程隔离的容器,您必须在创建容器时显式指定--isolation=process参数。正如我们之前提到的,还需要指定与您的操作系统匹配的容器镜像版本。让我们开始吧:

  1. 假设您正在运行 Windows 10,版本1903,让我们执行以下命令,尝试在分离(后台)模式下创建一个进程隔离的容器。运行 ping 命令,指定要发送到本地主机机器的回显请求的数量,即100
docker run -d --rm --isolation=process mcr.microsoft.com/windows/nanoserver:1809 cmd /c ping localhost -n 100

所选的 mcr.microsoft.com/windows/nanoserver 镜像版本为 1809,与您的操作系统版本不匹配。因此,它将因错误而失败,通知您容器的基本镜像操作系统版本与主机操作系统不匹配:

  1. 现在,让我们执行类似的命令,但现在指定正确的匹配版本(1903)的容器基本镜像:
docker run -d --rm --isolation=process mcr.microsoft.com/windows/nanoserver:1903 cmd /c ping localhost -n 100

在这种情况下,容器已成功启动,可以使用docker ps命令进行验证:

  1. 现在,让我们检查进程隔离在实践中与 Hyper-V 隔离有何不同。我们将比较这两种隔离类型之间主机 OS 中容器进程的可见性。

  2. 首先,获取您新创建的进程隔离容器的容器 ID。这个容器应该运行几分钟,因为它在终止并自动删除之前会执行 100 次对本地主机的回显请求。在我们的示例中,容器 ID 是a627beadb1297f492ec1f73a3b74c95dbebef2cfaf8f9d6a03e326a1997ec2c1。使用docker top <containerId>命令,可以列出容器内运行的所有进程,包括它们的进程 IDPID):

docker top a627beadb1297f492ec1f73a3b74c95dbebef2cfaf8f9d6a03e326a1997ec2c1

以下屏幕截图显示了上述命令的输出:

在上述屏幕截图中,容器内的ping.exe进程的 PID 为6420。为了列出在主机 OS 的上下文中运行的ping.exe进程,请在 Powershell 中使用Get-Process命令:

Get-Process -Name ping

以下屏幕截图显示了上述命令的输出:

上述输出显示,容器内运行的ping.exe进程也可以从主机上看到,并且 PID 完全相同:6420

为了进行比较,我们将创建一个类似的容器,但这次指定--isolation=hyperv参数以强制使用 Hyper-V 隔离。在 Windows 10 上,当运行默认的 Docker 配置时,可以完全省略--isolation参数,因为默认隔离级别是 Hyper-V。我们可以使用以下命令创建容器(使用与主机不同的基本镜像 OS 版本):

docker run -d --rm --isolation=hyperv mcr.microsoft.com/windows/nanoserver:1809 cmd /c ping localhost -n 100

以下屏幕截图显示了上述命令的输出:

容器已成功启动。在这种情况下,容器 ID 是c62f82f54cbce3a7673f5722e29629c1ab3d8a4645af9c519c0e60675730b66f。检查容器内运行的进程会发现ping.exe的 PID 为1268

当检查主机上运行的进程时,您会发现没有 PID 为1268ping.exe进程(也没有 PID 为1216cmd.exe进程,这是容器中的主要进程)。

这是因为在 Hyper-V 容器中运行的进程不会与主机共享内核,因为它们在单独的轻量级 Hyper-V VM 中执行,并且具有与容器基础镜像 OS 版本匹配的自己的内核。

现在,是时候在 Windows 上使用 LCOW 运行你的第一个 Linux 容器了!

运行 LCOW 容器

默认情况下,Docker Desktop for Windows 使用 MobyLinuxVM 托管 Linux 容器,为其提供了一个最小的、完全功能的环境。这种方法仅用于开发和测试目的,因为它在 Windows Server 上不可用。Windows Server 目前对 LCOW 有实验性支持,也可以在 Docker Desktop 中启用此功能。

要在 Docker Desktop 中启用 LCOW 支持,您必须在 Docker Daemon 中启用实验性功能。让我们来看一下:

  1. 打开 Docker Desktop 托盘图标并选择设置。

  2. 导航到 Daemon 选项卡。

  3. 启用实验性功能复选框:

  1. 应用更改。 Docker Desktop 将重新启动。

打开 PowerShell 并创建一个使用 Linux 作为基础镜像的容器,通过提供 --platform=linux 参数给 docker run。在这个例子中,我们以交互模式创建一个 busybox 容器,并启动 Bourne shell:

docker run --rm -it --platform=linux busybox /bin/sh

如果镜像存在一个平台的版本,则不需要提供 --platform 参数。下载镜像后,也不再需要指定 --platform 参数来运行容器。

容器启动后,Bourne shell 提示符将出现 (/ #)。现在,您可以使用 uname 命令验证您确实在 Linux 容器内运行,该命令会打印 Linux 内核信息:

uname -a

以下截图显示了前面命令的输出:

在一个单独的 Powershell 窗口中,在不关闭容器中的 Bourne shell 的情况下,执行 docker inspect <containerId> 命令以验证容器确实是使用 LCOW 使用 Hyper-V 隔离运行的:

在本节中,您学习了如何安装 Docker Desktop for Windows 工具和验证其功能,包括在 Windows 上运行 Linux 容器。在下一节中,您将学习如何使用 Visual Studio Code 来构建您的第一个 Windows 容器镜像。

构建你的第一个容器

在上一节中,您已经学会了如何在 Windows 上安装 Docker Desktop 以及如何运行简单的 Windows 和 Linux 容器。本节将演示如何使用 Dockerfile 构建自定义 Docker 镜像,以及如何执行运行容器的最常见操作,例如访问日志和执行exec进入容器。

Dockerfile 是一个文本文件,包含用户执行的所有命令,以组装容器镜像。由于本书不仅关注 Docker,本节将简要回顾常见的 Docker 操作。如果您对 Dockerfile 本身和构建容器感兴趣,请参考官方文档:docs.docker.com/engine/reference/builder/

例如,我们将准备一个 Dockerfile,创建一个 Microsoft IIS 的 Windows 容器镜像,托管一个演示 HTML 网页。为了演示操作原则,镜像定义不会很复杂。

准备 Visual Studio Code 工作区

第一步是准备 Visual Studio Code 工作区。Visual Studio Code 需要您安装一个额外的扩展来管理 Docker。让我们开始吧:

  1. 为了做到这一点,按Ctrl+Shift+X打开扩展视图。

  2. 在扩展:市场中,搜索docker并安装微软官方的 Docker 扩展:

本节演示的所有操作都可以在任何代码/文本编辑器和使用命令行中执行,而无需使用 Visual Studio Code。Visual Studio Code 是一个有用的多平台 IDE,用于开发和测试在 Docker 容器中运行的应用程序。

安装完成后,Docker Explorer 将可用:

  1. 您还可以在按下Ctrl+Shift+P 后,输入docker到搜索栏中,从命令面板中利用新的面向 Docker 的命令。

  1. 现在,通过使用Ctrl+KCtrl+O快捷键初始化工作区,打开所需的文件夹或导航到文件|打开文件夹...。

在下一小节中,我们将创建一个演示 HTML 网页,该网页将托管在 Windows 容器中。

创建一个示例 HTML 网页

我们将通过创建一个简约的 HTML“Hello World!”网页来开始创建我们的 Docker 镜像。这一步骤模拟了在没有任何容器化的情况下实现应用程序,并且在应用程序开发中是一个常见的场景:您正在运行一个非容器化的应用程序,然后将其移动到 Docker 容器中。

您还可以使用本书的 GitHub 存储库中的文件来执行此操作,该存储库位于:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter01/01_docker-helloworld-iis

使用Ctrl + N快捷键或通过导航到文件 > 新建文件在 Visual Studio Code 中的工作区中添加一个新文件。在新文件中使用以下示例 HTML 代码:

<!DOCTYPE html>
<html>
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <h1>Hello World from Windows container!</h1>
    </body>
</html>

将文件保存(使用Ctrl + S)为index.html在您的工作区中。

让我们继续创建 Dockerfile 本身。

创建 Dockerfile

由于我们将在容器中使用 IIS 托管网页,因此我们需要创建一个Dockerfile,该文件使用mcr.microsoft.com/windows/servercore/iis官方镜像作为构建的基础镜像。我们将使用带有windowsservercore-1903标签的 Docker 镜像,以确保我们运行与主机操作系统匹配的版本,并使其能够使用进程隔离。

在您的工作区中创建一个名为Dockerfile的新文件,其中包含以下内容:

FROM mcr.microsoft.com/windows/servercore/iis:windowsservercore-1903

RUN powershell -NoProfile -Command Remove-Item -Recurse C:\inetpub\wwwroot\*
WORKDIR /inetpub/wwwroot
COPY index.html .

在编写 Dockerfile 时,Visual Studio Code 会提供许多代码片段,前提是您已经按照预期的约定命名了文件。您还可以在编辑时按Ctrl + SPACE 来显示代码片段列表。

在下一小节中,您将学习如何根据刚刚创建的 Dockerfile 手动构建 Docker 镜像。

构建 Docker 镜像

使用docker build命令来执行构建 Docker 镜像。在执行此步骤时,您有两个选项:

  • 使用 Visual Studio Code 的命令面板。

  • 使用 Powershell 命令行。

在 Visual Studio Code 中,执行以下操作:

  1. 使用Ctrl + Shift + P快捷键打开命令面板。

  2. 搜索 Docker: Build Image 并按照以下格式执行它,提供镜像名称和标签(或者使用基于目录名称的默认建议名称):

<image name>:<tag>
  1. 如果您已登录到自定义注册表或使用 Docker Hub,您还可以指定以下内容:
<registry or username>/<image name>:<tag>

Docker Registry 和公共 Docker Hub 的概念将在第三章中进行介绍,使用容器镜像

在本示例中,我们将使用以下镜像名称和标签:docker-helloworld-iis:latest

Visual Studio Code 命令相当于在 Powershell 中执行以下操作:

  1. 将工作目录更改为包含Dockerfile的文件夹;例如:
cd c:\src\Hands-On-Kubernetes-on-Windows\Chapter01\docker-helloworld-iis
  1. 执行docker build命令,同时指定-t参数以提供镜像名称和标签,并使用当前目录.作为构建上下文:
docker build -t docker-helloworld-iis:latest .

以下屏幕截图显示了前述命令的输出:

成功构建后,您可以使用docker-helloworld-iis本地镜像来创建新的容器。我们将在下一小节中介绍这个过程。

运行 Windows 容器

现在,让我们使用示例网页创建一个进程隔离的 Windows 容器。在 Visual Studio Code 中,导航至命令面板(Ctrl + Shift + P),找到 Docker: Run 命令。选择docker-helloworld-iis作为镜像。将打开一个带有适当命令的终端。

这相当于在 Powershell 中执行docker run命令,如下(如果您的主机机器上的端口tcp/80已被占用,请使用其他可用端口):

docker run -d --rm --isolation=process -p 80:80 docker-helloworld-iis

成功启动容器后,通过网络浏览器导航至http://localhost:80/。您应该会看到以下输出:

接下来,我们将检查容器日志,这是调试容器问题最有用的工具之一。

检查容器日志

访问容器中主进程的标准输出和标准错误日志对于调试容器化应用程序的问题至关重要。这在使用 Kubernetes 时也是常见的情况,您可以使用 Kubernetes CLI 工具执行类似的操作。

官方 Microsoft IIS Docker 镜像的当前架构不会将任何日志输出到ServiceMonitor.exe(容器中的主进程)的stdout,因此我们将在之前使用的简单ping.exe示例上进行演示。运行以下容器以创建容器:

docker run -d --rm --isolation=process mcr.microsoft.com/windows/nanoserver:1903 cmd /c ping localhost -n 100

现在,在 Visual Studio Code 中,您可以通过打开命令面板(Ctrl + Shift + P)并执行Docker: Show Logs命令来检查日志。选择容器名称后,日志将显示在终端中。或者,您可以使用 Docker Explorer 选项卡,展开容器列表,右键单击要检查的容器,然后选择显示日志:

这将在 Visual Studio Code 中打开一个终端,以便您可以开始从容器的stdoutstderr实例中流式传输日志。

对于 PowerShell 命令行,您必须使用docker logs命令:

docker logs <containerId>

值得注意的是,在调试场景中,您可能会发现-f--tail参数很有用:

docker logs -f --tail=<number of lines> <containerId>

-f参数指示实时跟踪日志输出,而--tail参数使其仅显示输出的最后几行。

除了检查容器日志之外,您经常需要exec进入正在运行的容器。这将在下一小节中介绍。

进入正在运行的容器

在调试和测试场景中,通常需要以临时方式在运行的容器内执行另一个进程。这对于在容器中创建一个 shell 实例(对于 Windows,使用cmd.exepowershell.exe,对于 Linux,使用bashsh)并进行交互式调试特别有用。这样的操作称为执行exec进入正在运行的容器。

Visual Studio Code 通过 Docker Explorer 实现了这一点。在 Docker Explorer 选项卡中,找到要进入的容器,右键单击它,然后选择附加 Shell:

默认情况下,对于 Windows 容器,此命令将使用powershell.exe命令进行 exec。如果您正在运行基于 Windows Nano Server 的映像,则将无法使用powershell.exe,而必须改用cmd.exe。要自定义在附加 Shell 期间使用的命令,请打开设置(Ctrl + ,),搜索 docker,并自定义 docker.attachShellCommand.windowsContainer 设置。

在 Powershell 命令行中,等效的docker exec命令如下:

docker exec -it <containerId> powershell.exe

上述命令在附加终端(-it参数)的交互模式下在运行的容器中创建了一个新的powershell.exe进程。如您所见,Powershell 终端的新交互式实例已打开:

您只能进入正在运行主进程的容器。如果容器已退出、终止或处于暂停状态,则无法使用exec命令。

让我们尝试检查容器工作目录中index.html的内容:

cat .\index.html

以下截图显示了前述命令的输出:

这显示了我们之前创建并添加到镜像中的index.html文件的预期内容。

我们还可以检查托管index.html的应用程序池的 IIS 工作进程(w3wp.exe)。这是在调试期间的常见场景,当不是所有日志都直接通过容器输出日志可用时:

cat ..\logs\LogFiles\W3SVC1\u_ex<current date>.log

以下截图显示了前述命令的输出:

使用docker exec是您容器工具箱中最强大的命令之一。如果您学会如何使用它,您将能够几乎像在非容器化环境中托管应用程序一样进行调试。

摘要

在本章中,您了解了 Windows 容器架构的关键方面以及 Windows 容器运行时提供的隔离模式之间的区别。我们还介绍了如何在 Windows 平台上安装 Docker Desktop,并演示了如何使用 Docker CLI 执行最重要的操作。

本章和接下来的两章将是本书其余部分关于 Windows 上 Kubernetes 的基础。在下一章中,我们将专注于在 Windows 容器中管理状态,即在运行容器时如何持久化数据。

问题

  1. Windows 暴露哪些内核特性以实现容器化?

  2. 在 Linux 和 Windows 上容器化之间的主要区别是什么?

  3. Hyper-V 隔离和进程隔离之间有什么区别?何时应该使用 Hyper-V 隔离?

  4. 我们如何在 Windows 10 上启用 LCOW?

  5. 我们可以使用什么命令来访问 Docker 容器中主进程的日志?

  6. 我们如何在运行的容器内启动一个新的 Powershell 进程?

您可以在本书的评估部分找到这些问题的答案。

进一步阅读

本章对 Windows 上的 Docker 容器进行了回顾。有关 Windows 容器的更多信息,请参考两本优秀的 Packt 图书。

第二章:在容器中管理状态

管理应用程序的状态是架构任何软件解决方案时的关键方面之一,无论是单体桌面应用程序还是托管在云环境中的复杂分布式系统。即使系统中的大多数服务都是无状态的,系统的某些部分将是有状态的,例如,托管在云中的 NoSQL 数据库或您自己实现的专用服务。如果您希望设计具有良好可扩展性,您必须确保有状态服务的存储能够适当扩展。在这些方面,托管在 Docker 容器中的服务或应用程序并无二致-您需要管理状态,特别是如果您希望数据在容器重新启动或失败时得以持久化。

在本章中,我们将为您提供更好地理解如何在运行在 Windows 上的 Docker 容器中持久化状态以及这些概念与 Kubernetes 应用程序中数据持久性的关系。您将了解volumesbind mounts的概念以及它们如何用于在容器和容器主机之间共享状态。

本章涵盖以下主题:

  • 挂载本地卷以用于有状态的应用程序

  • 使用远程/云存储进行容器存储

  • 在容器内运行集群解决方案

技术要求

对于本章,您将需要以下内容:

  • 已安装 Windows 10 Pro、企业版或教育版(版本 1903 或更高版本,64 位)

  • 已安装 Docker Desktop for Windows 2.0.0.3 或更高版本

Docker Desktop for Windows 的安装及其详细要求在第一章*,创建容器*中已经涵盖。

您还需要自己的 Azure 帐户。您可以在此处阅读有关如何获取个人使用的有限免费帐户的更多信息:azure.microsoft.com/en-us/free/

您可以从本书的官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter02

挂载本地卷以用于有状态的应用程序

要了解有状态应用程序的本机 Docker 存储选项,我们必须看一下层文件系统的组织方式。这个文件系统服务的主要作用是为每个基于 Docker 镜像的容器提供一个单一的虚拟逻辑文件系统。

Docker 镜像由一系列只读层组成,其中每个层对应于 Dockerfile 中的一个指令。让我们来看看上一章中的以下 Dockerfile:

FROM mcr.microsoft.com/windows/servercore/iis:windowsservercore-1903

RUN powershell -NoProfile -Command Remove-Item -Recurse C:\inetpub\wwwroot\*
WORKDIR /inetpub/wwwroot
COPY index.html .

构建 Docker 镜像时,(几乎)每个指令都会创建一个新的层,其中包含给定命令引入的文件系统中的一组差异。在这种情况下,我们有以下内容:

  • FROM mcr.microsoft.com/windows/servercore/iis:windowsservercore-1903:这个指令定义了基础层(或一组层)来自基础镜像。

  • RUN powershell -NoProfile -Command Remove-Item -Recurse C:\inetpub\wwwroot\*:这个指令创建的层将反映从原始基础镜像中删除C:\inetpub\wwwroot\目录中内容。

  • WORKDIR /inetpub/wwwroot:即使这个指令不会引起任何文件系统的更改,它仍然会创建无操作nop)层来保留这些信息。

  • COPY index.html .:这个最后的指令创建了一个层,其中包含C:\inetpub\wwwroot\目录中的index.html

如果您有现有的 Docker 镜像,可以使用docker history命令自己检查层:

docker history <imageName>

例如,对于前面的 Dockerfile 生成的图像,您可以期望以下输出:

底部的五个层来自mcr.microsoft.com/windows/servercore/iis:windowsservercore-1903基础镜像,而顶部的三个层是我们之前描述的指令的结果。

当创建一个新的容器时,为其创建文件系统,其中包括只读的镜像层和一个可写的顶层,也称为容器层。对于容器,这些层是透明的,进程“看到”它就像是一个常规的文件系统 - 在 Windows 系统上,这是由Windows 容器隔离文件系统服务*保证的。容器内部的进程对容器文件系统所做的任何更改都会在可写层中持久保存。这个概念可以在以下图表中看到:

现在我们了解了 Docker 中层文件系统的原则,我们可以专注于绑定挂载

卷和绑定挂载

此时,似乎为每个容器都有一个可写的容器层就足以为应用程序提供状态持久性。即使您停止并重新启动同一个容器,数据也会被持久化。不幸的是,您会很容易发现容器及其可写层是紧密耦合的,您无法轻松地在不同的容器之间甚至同一图像的新实例之间共享数据。一个简单的情景是:

  1. 基于当前的 Dockerfile 构建一个容器镜像。

  2. 基于此构建启动一个新的容器。

  3. 对可写容器层进行一些修改;例如,容器内的一个进程修改了存储应用程序数据的文件。

  4. 现在,您想通过修改 Dockerfile 并添加额外的指令来创建图像的新版本。与此同时,您希望重新创建容器,并重用已经在容器的可写层中修改过的文件中的数据。

您会意识到,使用新的图像版本重新创建容器后,您使用应用程序状态对文件所做的所有更改都将消失。除此之外,使用容器层来存储数据还有更多的缺点:

  • 可写层与容器主机紧密耦合,这意味着无法轻松地将数据移动到不同的主机。

  • 层文件系统的性能比直接访问主机文件系统差。

  • 您不能在不同的容器之间共享可写层。

一个经验法则是要避免将数据存储在可写的容器层中,特别是对于 I/O 密集型应用程序。

Docker 提供了两种持久存储的解决方案,可以挂载到容器中:卷和绑定挂载。在这两种情况下,数据都会暴露为容器文件系统中的一个目录,并且即使容器停止和删除,数据也会被持久化。在性能方面,卷和绑定挂载都直接访问主机的文件系统,这意味着没有层文件系统的开销。还可以使用这些 Docker 功能在多个容器之间共享数据。

绑定挂载提供了一个简单的功能,可以将容器主机中的任何文件目录挂载到给定的容器中。这意味着绑定挂载将充当主机和容器之间共享的文件或目录。一般来说,不建议使用绑定挂载,因为它们比卷更难管理,但在某些情况下,绑定挂载是有用的,特别是在 Windows 平台上,卷支持有限。

绑定挂载允许您共享容器主机中的任何文件。这意味着,如果您将敏感目录(例如C:\Windows\)挂载到一个不受信任的容器中,您就会面临安全漏洞的风险。

卷提供了与绑定挂载类似的功能,但它们由 Docker 完全管理,这意味着您不必担心容器主机文件系统中的物理路径。您可以创建匿名命名卷,然后将它们挂载到容器中。除非您明确使用 Docker 删除卷,否则卷中的任何数据都不会被删除。卷的一个非常常见的用例是为运行数据库实例的容器提供持久存储 - 当容器被重新创建时,它将使用包含前一个容器实例写入的数据的相同卷。

卷的基本功能是在容器主机文件系统中提供存储。还可以使用卷驱动程序插件),它们使用卷抽象来访问远程云存储或网络共享。请注意,目前在 Windows 平台上,对卷插件的支持有限,大多数插件只能在 Linux 操作系统上使用。有关可用插件的更多信息,请访问docs.docker.com/engine/extend/legacy_plugins/#volume-plugins

现在,让我们看看如何在 Docker 卷上执行基本操作。

创建和挂载卷

可以使用docker volume create命令显式地创建新卷。还可以在容器启动时自动创建命名卷和匿名卷。要手动创建 Docker 命名卷,请按照以下步骤进行操作:

  1. 执行以下命令:
docker volume create <volumeName>
  1. 创建后,可以使用docker volume inspect命令检查卷的详细信息:

如您所见,使用默认的local驱动程序时,卷数据存储为主机文件系统中的常规目录。

要将卷挂载到容器中,您必须使用docker run命令的--mount--volume(简称参数:-v)参数。最初,--volume用于独立容器,而--mount用于集群容器,但从 Docker 17.06 开始,--mount也可以用于独立容器,并且是推荐的做法,因为它提供了更强大的选项。有关这些标志的更多信息可以在官方文档中找到:docs.docker.com/storage/volumes/#choose-the--v-or---mount-flag

按照以下步骤学习如何挂载卷:

  1. 假设您想要在新的 PowerShell 容器中将test-named-volume从上一个示例挂载到C:\Data目录下,您必须指定--mount参数,如下所示:
docker run -it --rm `
 --isolation=process `
 --mount source=test-named-volume,target=C:\Data `
 mcr.microsoft.com/powershell:windowsservercore-1903

省略source=<volumeName>参数将导致创建一个匿名卷,稍后可以使用卷 ID 访问。请记住,如果您使用--rm选项运行容器,匿名卷将在容器退出时自动删除。

  1. 容器启动并且终端已附加后,请尝试在已挂载卷的目录中创建一个简单的文件:
echo "Hello, Volume!" > C:\Data\test.txt
  1. 现在,退出容器(这将导致容器停止并由于--rm标志而自动删除),并在主机上检查卷目录:
PS C:\WINDOWS\system32> cat C:\ProgramData\Docker\volumes\test-named-volume\_data\test.txt
Hello, Volume!
  1. 为了证明命名卷可以轻松地挂载到另一个容器中,让我们基于mcr.microsoft.com/windows/servercore:1903镜像创建一个新的容器,并且挂载目标与上一个示例中的不同:
docker run -it --rm `
 --isolation=process `
 --mount source=test-named-volume,target=C:\ServerData `
 mcr.microsoft.com/windows/servercore:1903
  1. 如果您检查容器中的卷目录,您会注意到test.txt文件存在并包含预期的内容:
C:\>more C:\ServerData\test.txt
Hello, Volume!

还可以在 Dockerfile 中使用VOLUME命令,以便在容器启动时强制自动创建卷,即使未为docker run命令提供--mount参数。如果您希望明确告知其他人应用程序的状态数据存储在何处,以及需要确保层文件系统不会引入额外的开销,这将非常有用。您可以在本书存储库中的以下 Dockerfile 中找到VOLUME命令的用法示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter02/03_MongoDB_1903/Dockerfile#L44

在下一小节中,我们将快速了解如何使用 Docker CLI 删除卷。

删除卷

要使用docker volume rm命令删除现有的命名或匿名卷,它们不能在任何容器中使用(即使是停止的容器)。标准流程如下:

docker stop <containerId>
docker rm <containerId>
docker volume rm <volumeId>

对于匿名卷,如果在docker run命令中使用--rm标志,容器将在退出时被删除,连同其匿名卷。这个标志应根据情况使用——在大多数情况下,它对于测试和开发目的很有用,以便更轻松地进行清理。

在开发过程中,您可能偶尔需要对 Docker 主机上的所有卷进行全面清理,例如,如果您需要释放磁盘空间。Docker CLI 提供了一个专用命令,将删除任何未在任何容器中使用的卷:

docker volume prune

接下来,我们将看一下绑定挂载及其与卷的区别。

使用绑定挂载挂载本地容器主机目录

绑定挂载是容器和主机机器之间共享的最简单的持久存储形式。通过这种方式,您可以在容器中挂载主机文件系统中的任何现有目录。还可以使用主机目录内容“覆盖”容器中的现有目录,这在某些情况下可能很有用。一般来说,卷是推荐的存储解决方案,但有一些情况下绑定挂载可能会有用:

  • 在主机和容器之间共享配置。一个常见的用例可能是 DNS 配置或hosts文件。

  • 在开发场景中,共享在主机上创建的构建产物,以便它们可以在容器内使用。

  • 在 Windows 上,将 SMB 文件共享挂载为容器中的目录。

卷可以被视为绑定挂载的 演进。它们由 Docker 完全管理,用户看不到与容器主机文件系统的紧密耦合。

为容器创建绑定挂载需要在 docker run 命令中指定一个额外的参数 type=bind,用于 --mount 标志。在这个例子中,我们将主机的 C:\Users 目录挂载为容器中的 C:\HostUsers

docker run -it --rm `
 --isolation=process `
 --mount type=bind,source=C:\Users,target=C:\HostUsers `
 mcr.microsoft.com/powershell:windowsservercore-1903

您可以验证对 C:\HostUsers 所做的任何更改也会在主机机器的 C:\Users 中可见。

有关 Windows 特定功能和绑定挂载的限制,请参阅 Microsoft 的官方文档:docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-storage#bind-mounts

在下一节中,我们将学习如何利用绑定挂载来在 Windows 容器中使用远程或云存储。

使用远程/云存储作为容器存储

在容器主机文件系统中存储数据不适用于需要高可用性、故障转移和数据备份便捷性的用例。为了提供存储抽象,Docker 提供了卷驱动程序(插件),可用于管理托管在远程机器或云服务中的卷。不幸的是,在撰写本书时,运行在本地的 Windows 容器不支持当前在 Linux OS 上可用的卷插件。这使我们在使用 Windows 容器中的云存储时有三种选择:

  • 使用 Docker for Azure 和 Cloudstor 卷插件,这是在 Azure VM 上以 swarm 模式运行 Docker 的部分托管解决方案。在本书中,我们不会涵盖 Docker for Azure,因为这个解决方案与 Kubernetes 分开,包括 Azure 提供的托管 Kubernetes 的服务。如果您对此服务的更多细节感兴趣,请参阅 docs.docker.com/docker-for-azure/persistent-data-volumes/

  • 在应用程序代码中直接使用云存储,使用云服务提供商的 SDK。这是最明显的解决方案,但它需要将存储管理嵌入到应用程序代码中。

  • 使用绑定挂载和服务器消息块SMB)全局映射来挂载 Azure Files,这是一个完全托管的云文件共享,可以通过 SMB 协议访问。

很快,我们将演示如何利用最后一个选项:Azure Files 的 SMB 全局映射。但首先,我们必须安装 Azure CLI 以管理 Azure 资源。

安装 Azure CLI 和 Azure PowerShell 模块

为了从命令行高效地管理 Azure 资源,建议使用官方的 Azure CLI。官方安装说明可以在docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows?view=azure-cli-latest找到。让我们开始吧:

  1. 从 PowerShell 安装 Azure CLI 需要以管理员身份运行以下命令:
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'
Remove-Item .\AzureCLI.msi
  1. 安装 Azure CLI 后,您需要重新启动 PowerShell 窗口。接下来,登录到您的 Azure 帐户:
az login

上述命令将打开您的默认浏览器,并指示您登录到您的 Azure 帐户。

  1. 现在,运行以下命令来验证您已经正确登录:
az account show

您应该能够看到您的订阅详细信息,类似于这样:

除此之外,我们还需要安装 Azure PowerShell 模块,因为一些操作在 Azure CLI 中不可用。

  1. 可以使用以下命令为当前登录的用户安装:
Install-Module -Name Az -AllowClobber -Scope CurrentUser

官方安装步骤可以在这里找到:docs.microsoft.com/en-us/powershell/azure/install-az-ps?view=azps-2.5.0#install-the-azure-powershell-module-1

  1. 如果在导入新安装的模块时遇到问题,您还需要以管理员身份设置 PowerShell 执行策略为RemoteSigned
Set-ExecutionPolicy RemoteSigned
  1. 使用 PowerShell 模块登录到 Azure 必须与 Azure CLI 分开进行,并可以使用以下命令执行:
Connect-AzAccount

此时,您应该能够使用 Azure CLI 和 Azure PowerShell 模块来管理您的资源,而无需打开 Azure 门户网站!让我们看看如何创建 Azure Files SMB 共享。

创建 Azure Files SMB 共享

假设您正在使用全新的 Azure 订阅进行这些示例演练,让我们首先创建一个 Azure 资源组和 Azure 存储帐户:

  1. 在 PowerShell 窗口中,执行以下代码:
az group create `
 --name docker-storage-resource-group `
 --location westeurope

您可以选择最适合您的位置(为了显示可用位置的列表,请运行az account list-locations)。在这个例子中,我们使用westeurope Azure 位置。

您还可以使用本书的 GitHub 存储库中的 PowerShell 脚本来执行此操作:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter02/01_CreateAzureFilesSMBShare.ps1。请记住以管理员身份运行此脚本,因为必须从特权帐户添加 SMB 共享的全局映射。

  1. 成功创建 Azure 资源组后,继续创建 Azure 存储帐户:
az storage account create `
 --name dockerstorageaccount `
 --resource-group docker-storage-resource-group `
 --location westeurope `
 --sku Standard_RAGRS `
 --kind StorageV2

上述命令将在docker-storage-resource-group中创建一个名为dockerstorageaccountgeneral-purpose v2存储帐户,其中包含read-access geo-redundant存储。此操作可能需要几分钟才能完成。

  1. 接下来,您必须创建实际的 Azure Files SMB 共享。首先,为您的 Azure 存储帐户创建连接字符串,并将其存储为 PowerShell 中的变量:
$azureStorageAccountConnString = az storage account show-connection-string `
 --name dockerstorageaccount `
 --resource-group docker-storage-resource-group `
 --query "connectionString" `
 --output tsv

if (!$azureStorageAccountConnString) {
 Write-Error "Couldn't retrieve the connection string."
}

请记住保护好连接字符串,因为它可以用来管理您的存储帐户!

  1. 使用存储在$azureStorageAccountConnString变量中的连接字符串,创建共享:
az storage share create `
 --name docker-bind-mount-share `
 --quota 2 `
 --connection-string $azureStorageAccountConnString 

这将创建一个名为docker-bind-mount-share的共享,配额限制为 2GB,我们将在 Docker 容器中使用它。

在容器中挂载 Azure Files SMB 共享

为了将新的 Azure Files SMB 共享作为容器中的绑定挂载,我们将利用在 Window Server 1709 中引入的SMB 全局映射功能。全局映射是专门为此目的而引入的,即在主机上挂载 SMB 共享,以便它们对容器可见。让我们开始吧:

  1. 首先确保您已登录,以便可以执行 Azure PowerShell(使用Connect-AzAccount命令)。

  2. 接下来,让我们定义一些变量,这些变量将在我们即将执行的命令中使用:

$resourceGroupName = "docker-storage-resource-group"
$storageAccountName = "dockerstorageaccount"
$fileShareName = "docker-bind-mount-share"

这里使用的名称与我们在上一小节中创建 Azure Files SMB 共享时使用的名称完全相同。

  1. 下一步是定义$storageAccount$storageAccountKeys变量:
$storageAccount = Get-AzStorageAccount `
 -ResourceGroupName $resourceGroupName `
 -Name $storageAccountName
$storageAccountKeys = Get-AzStorageAccountKey `
 -ResourceGroupName $resourceGroupName `
 -Name $storageAccountName

这些变量将用于检索文件共享详细信息和访问凭据,这两者都是 SMB 全局映射所需的。

  1. 现在,可选地,您可以使用cmdkey命令将共享凭据持久保存在 Windows 凭据管理器中:
Invoke-Expression -Command `
 ("cmdkey /add:$([System.Uri]::new($storageAccount.Context.FileEndPoint).Host) " + `
 "/user:AZURE\$($storageAccount.StorageAccountName) /pass:$($storageAccountKeys[0].Value)")
  1. 我们还需要关于 Azure Files SMB 共享的详细信息,因此让我们定义一个名为$fileShare的新变量:
$fileShare = Get-AzStorageShare -Context $storageAccount.Context | Where-Object { 
    $_.Name -eq $fileShareName -and $_.IsSnapshot -eq $false
}
  1. 此时,您还可以检查文件共享详细信息是否已成功检索。通过这样做,您将能够检测出例如$fileShareName是否包含了错误的共享名称:
if ($fileShare -eq $null) {
    Write-Error "Azure File share not found"
}
  1. 在创建 SMB 全局映射之前的最后一步是定义一个凭据对象,该对象将用于映射创建:
$password = ConvertTo-SecureString `
    -String $storageAccountKeys[0].Value `
    -AsPlainText `
    -Force
$credential = New-Object System.Management.Automation.PSCredential `-ArgumentList "AZURE\$($storageAccount.StorageAccountName)", $password
  1. 最后,我们可以使用New-SmbGlobalMapping命令来为 Azure Files SMB 共享创建映射:
New-SmbGlobalMapping `
 -RemotePath "\\$($fileShare.StorageUri.PrimaryUri.Host)\$($fileShare.Name)" `
 -Credential $credential `
 -Persistent $true `
 -LocalPath G:

如果您需要删除 SMB 全局映射,可以使用Remove-SmbGlobalMapping命令来执行。

上述命令将持久地将 Azure Files SMB 共享挂载为G:驱动器。您可以稍后使用此路径进行 Docker 容器的绑定挂载。现在,您可以通过使用 Windows 资源管理器将一些测试文件移动到G:驱动器来测试您的映射是否正常工作。

使用绑定挂载进行全局映射的 SMB 共享的原则可以用于任何兼容 SMB 的服务器,例如以下服务器:

  • 在您的本地网络中托管的传统文件服务器

  • SMB 协议的第三方实现,例如 NAS 设备

  • 基于存储空间直通(S2D)的传统 SAN 或分布式文件服务器SoFS

当作为绑定挂载使用时,全局映射的 SMB 共享对容器来说是透明可见的,就像本地文件系统中的常规目录一样。所有的“繁重工作”都是由容器主机执行的,它负责管理 SMB 共享连接。

让我们通过创建一个简单的 PowerShell 进程隔离容器来演示这个功能:

  1. 首先,在我们的演示容器的 SMB 共享中创建一个名为G:\ContainerData的目录:
 New-Item -ItemType Directory -Force -Path G:\ContainerData
  1. 现在,我们可以通过将 Azure Files SMB 共享中的新目录作为绑定挂载并将C:\Data作为目标来运行容器:
docker run -it --rm `
 --isolation=process `
 --mount type=bind,source=G:\ContainerData,target=C:\Data               `mcr.microsoft.com/powershell:windowsservercore-1903

有了这个,我们可以轻松证明我们的解决方案有效,并且容器状态文件确实存储在 Azure Cloud 中!

  1. 在运行的容器中,创建一个包含数据的文件。例如,获取当前运行的进程列表,并将其存储为processes.txt文件:
Get-Process > C:\Data\processes.txt
  1. 现在,登录到 Azure 门户(https://portal.azure.com/)并执行以下操作:

  2. 从主菜单导航到存储账户。

  3. 打开 dockerstorageaccount 账户。

  4. 在存储账户菜单中,打开文件服务组下的文件。

  5. 从列表中打开 docker-bind-mount-share 文件共享。

您将看到一个熟悉的目录结构。进入 ContainerData 目录,看到processes.txt文件确实存在,并包含在容器中存储的数据:

在 Kubernetes 中,可以使用volumes(不要与 Docker 卷混淆)以受控方式执行类似的过程。我们将在第十一章中重点介绍这一点,配置应用程序以使用 Kubernetes 功能。您也可以参考官方文档:kubernetes.io/docs/concepts/storage/

请注意,这种情况也可以通过在本地网络中托管常规 SMB 文件服务器来实现,如果您已经在基础架构中使用它们,这可能是一个合适的解决方案。

恭喜!您已成功创建了一个使用 Azure Cloud 存储来持久保存容器状态的 Windows 容器。在下一节中,我们将学习如何在 Windows 容器中运行 MongoDB,作为多容器解决方案的示例。

在容器内运行集群解决方案

MongoDB 是一个免费的开源跨平台、面向文档的数据库程序,可以在集群模式下运行(使用分片和副本集)。在这个例子中,我们将运行一个三节点的 MongoDB 副本集,因为这比完整的分片集群更容易配置,并且足以演示持久存储容器状态数据的原理。

如果您想了解更多关于 MongoDB 和高级分片集群组件的信息,请参考官方文档:docs.mongodb.com/manual/core/sharded-cluster-components/

我们的 MongoDB 副本集架构将如下所示:

主节点负责管理所有写操作,在 ReplicaSet 中只能有一个主节点。次要节点只复制主节点的oplog并应用数据操作,以便它们的数据集反映主节点的数据集。这种 MongoDB 部署的主要好处如下:

  • 自动故障转移:如果主节点不可用,其余次要节点将执行新的领导者选举并恢复集群功能。

  • 可以使用次要节点读取数据:您可以指定读取偏好,以便客户端将主节点的读取操作卸载。但是,您必须注意异步复制可能导致次要节点与主节点略有不同步。

现在,让我们创建我们的 MongoDB ReplicaSet!

创建 MongoDB ReplicaSet

按照以下步骤创建 ReplicaSet:

  1. 首先,让我们使用docker network create命令为新集群创建一个名为mongo-cluster的 Docker 网络:
docker network create --driver nat mongo-cluster

如果您想了解有关 Docker 网络的更多信息,请参考官方文档:docs.docker.com/network/

有关特定于 Windows 的文档,请访问docs.microsoft.com/en-us/virtualization/windowscontainers/container-networking/network-drivers-topologies

我们将使用 Azure Files SMB 共享(全局映射到G:驱动器),这是我们在上一节中创建的,以便使用绑定挂载存储 MongoDB 的状态。

  1. 我们需要在我们的 SMB 共享中创建新的目录,每个 MongoDB 节点需要两个:
New-Item -ItemType Directory -Force -Path G:\MongoData1\db
New-Item -ItemType Directory -Force -Path G:\MongoData1\configdb
New-Item -ItemType Directory -Force -Path G:\MongoData2\db
New-Item -ItemType Directory -Force -Path G:\MongoData2\configdb
New-Item -ItemType Directory -Force -Path G:\MongoData3\db
New-Item -ItemType Directory -Force -Path G:\MongoData3\configdb

目前,Windows 的官方 MongoDB 镜像仅存在于 Windows Server Core 1803 中,这意味着我们必须使用 Hyper-V 隔离在 Windows 1903 上运行这些容器。这意味着我们无法利用 SMB 全局映射,因此我们需要基于 Windows Server Core 1903 创建自己的 MongoDB 镜像。这将使我们能够使用进程隔离。我们要构建的镜像是基于 4.2.0 RC8 版本的官方 MongoDB 镜像,可以在这里找到:github.com/docker-library/mongo/blob/a3a213fd2b4b2c26c71408761534fc7eaafe517f/4.2-rc/windows/windowsservercore-1803/Dockerfile。要执行构建,请按照以下步骤进行:

  1. 从本书的 GitHub 存储库下载 Dockerfile:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter02/03_MongoDB_1903/Dockerfile

  2. 在 PowerShell 中,导航到您下载 Dockerfile 的位置(建议使用新的、单独的目录)。

  3. 执行docker build命令,以在本地镜像注册表中创建一个名为mongo-1903的自定义 MongoDB 镜像:

docker build -t mongo-1903:latest .

构建过程将需要几分钟,因为 MongoDB 必须在构建容器中下载和安装。

该镜像还将 MongoDB 数据公开为容器内的C:\data\dbC:\data\configdb卷(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter02/03_MongoDB_1903/Dockerfile#L44)。考虑到所有这些,让我们创建我们的第一个 MongoDB 进程隔离容器,命名为mongo-node1,它将在后台运行(使用-d选项):

docker run -d `
 --isolation=process `
 --volume G:\MongoData1\db:C:\data\db `
 --volume G:\MongoData1\configdb:C:\data\configdb `
 --name mongo-node1 `
 --net mongo-cluster `
 mongo-1903:latest `
 mongod --bind_ip_all --replSet replSet0

在运行此容器时,我们提供了一个自定义命令来运行容器进程,即mongod --bind_ip_all --replSet replSet0--bind_ip_all参数指示 MongoDB 绑定到容器中可用的所有网络接口。对于我们的用例,--replSet replSet0参数确保守护程序以 ReplicaSet 模式运行,期望在名为replSet0的 ReplicaSet 中。

成功创建第一个节点后,重复此过程用于下两个节点,适当更改它们的名称和卷挂载点:

docker run -d `
 --isolation=process `
 --volume G:\MongoData2\db:C:\data\db `
 --volume G:\MongoData2\configdb:C:\data\configdb `
 --name mongo-node2 `
 --net mongo-cluster `
 mongo-1903:latest `
 mongod --bind_ip_all --replSet replSet0

docker run -d `
 --isolation=process `
 --volume G:\MongoData3\db:C:\data\db `
 --volume G:\MongoData3\configdb:C:\data\configdb `
 --name mongo-node3 `
 --net mongo-cluster `
 mongo-1903:latest `
 mongod --bind_ip_all --replSet replSet0

创建过程完成后,您可以使用 docker ps 命令验证容器是否正常运行:

上述步骤也已经作为 PowerShell 脚本提供在本书的 GitHub 仓库中:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter02/02_InitializeMongoDBReplicaset.ps1

接下来的阶段是配置 ReplicaSet。我们将使用 mongo shell 来完成这个过程。按照以下步骤进行:

  1. 创建一个 mongo shell 实例。如果您已经运行了一个 MongoDB 容器(例如,mongo-node1),最简单的方法是 exec 进入现有容器并运行 mongo 进程:
docker exec -it mongo-node1 mongo
  1. 几秒钟后,您应该会看到 mongo shell 控制台提示符 >。您可以使用 rs.initiate() 方法初始化 ReplicaSet:
rs.initiate(
  {
    "_id" : "replSet0",
    "members" : [
      { "_id" : 0, "host" : "mongo-node1:27017" },
      { "_id" : 1, "host" : "mongo-node2:27017" },
      { "_id" : 2, "host" : "mongo-node3:27017" }
    ]
  }
)

上述命令使用我们的三个节点在 mongo-cluster Docker 网络中创建了一个名为 replSet0 的 ReplicaSet。这些节点可以通过它们在 mongo-cluster Docker 网络中的 DNS 名称进行识别。

有关初始化 ReplicaSets 的更多详细信息,请参考官方文档:docs.mongodb.com/manual/reference/method/rs.initiate/

  1. 您还可以使用 mongo shell 中的 rs.status() 命令来验证初始化状态。在一段时间后,当 ReplicaSet 完全初始化时,在命令的 JSON 输出中,您应该能够看到一个节点中的 ReplicaSet 的 "stateStr": "PRIMARY",以及另外两个节点中的 "stateStr": "SECONDARY"

在下一小节中,我们将通过在另一个容器中生成测试数据并读取它来快速验证我们的 ReplicaSet。

编写和读取测试数据

按照以下步骤编写和读取测试数据:

  1. 首先,在 ReplicaSet 主节点的 mongo shell 中(提示符为 replSet0:PRIMARY> ),让我们在 demo 集合中添加 1,000 个示例文档:
for (var i = 1; i <= 1000; i++) {
 db.demo.insert( { exampleValue : i } )
}
  1. 您可以使用 demo 集合上的 find() 方法快速验证插入的文档:
db.demo.find()
  1. 现在,我们将创建一个在 Docker 容器中运行的最小化.NET Core 3.0 控制台应用程序。这将连接到运行在我们的 Docker 容器中的 ReplicaSet,查询我们的demo集合,并将每个文档的exampleValue值写入标准输出。

您可以在本书的 GitHub 存储库中找到此源代码和 Dockerfile:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter02/04_MongoDB_dotnet

如果在执行此场景时,您遇到任何与 MongoDB 的不稳定问题,请考虑将mongo-1903 Dockerfile 升级到最新的 MongoDB 版本。

为了读取我们的测试数据,我们需要构建应用程序 Docker 镜像,并创建一个在mongo-cluster网络中运行的容器。执行以下步骤:

  1. 克隆存储库并在 PowerShell 中导航到Chapter02/04_MongoDB_dotnet目录。

  2. 在当前目录中执行docker build以创建mongo-dotnet-sample Docker 镜像:

docker build -t mongo-dotnet-sample:latest .
  1. 运行示例容器。这需要连接到mongo-cluster网络:
docker run --isolation=process `
 --rm `
 --net mongo-cluster `
 mongo-dotnet-sample:latest

在输出中,您应该看到一个递增的数字序列,这是我们测试文档中exampleValue的值:

如果您感兴趣,可以在 Azure 门户上检查 SMB 共享包含什么内容(portal.azure.com/):

恭喜!您已成功创建并测试了在 Windows 容器中运行的 MongoDB ReplicaSet,并使用 Azure Files SMB 共享作为绑定挂载来存储数据。让我们快速总结一下本章学到的内容。

总结

在本章中,您学习了 Windows 上 Docker 存储的关键方面:使用卷和绑定挂载。在 Azure 的帮助下,您成功设置了 Azure Files SMB 共享,可以使用 SMB 全局映射来存储容器状态数据。最后,您通过设置自己的由 Azure 云存储支持的三节点 MongoDB ReplicaSet 来总结了所有这些,并验证了您的设置!

下一章将是最后一章专注于 Windows 平台上的 Docker。您可以期待学习如何使用 Docker 镜像以及如何在应用程序开发生命周期中使用它们的基础知识。之后,我们将准备开始我们的 Kubernetes 之旅。

问题

  1. Docker 存储架构中的容器层是什么?

  2. 卷和绑定挂载之间有什么区别?

  3. 为什么不建议将容器状态数据存储在容器层中?

  4. 如何在容器中透明地挂载 Azure Files SMB 共享?

  5. 在运行在 Hyper-V 隔离中的容器中可以使用绑定挂载吗?

  6. 什么命令可以删除容器主机上的所有未使用卷?

  7. 什么是卷驱动程序(插件)?

您可以在本书的评估部分找到这些问题的答案。

进一步阅读

第三章:使用容器图像

基于容器的软件开发生命周期需要简单的图像打包和可靠的分发容器化应用程序的方法-这些是 Docker 生态系统解决的关键方面。我们在前几章中使用了 Dockerfiles 和 Docker 图像。简单来说,Dockerfile 定义了用于创建 Docker 图像的构建指令,这是容器数据的不可变的、分层的快照,可用于实例化容器。这两个概念使我们能够为容器应用程序创建简单和标准化的打包。为了为 Docker 图像提供可靠和可扩展的分发,我们可以使用图像注册表。

在本章中,我们将重点介绍 Docker 注册表的使用,主要是公开访问的 Docker Hub 和私有的 Azure 容器注册表,并且我们还将介绍 Docker 内容信任-用于发布和管理已签名内容集合的概念。通过这个容器图像管理的介绍,您将准备好完全进入 Windows 上的 Kubernetes 世界!

本章将涵盖以下主题:

  • 存储和共享容器图像

  • 使用云容器构建器

  • 图像标记和版本控制

  • 确保图像供应链的完整性

技术要求

本章,您将需要以下内容:

  • 已安装 Windows 10 专业版、企业版或教育版(1903 版或更高版本,64 位)。

  • 已安装 Docker Desktop for Windows 2.0.0.3 或更高版本。

  • 已安装 Azure CLI。您可以在第二章*,在容器中管理状态*中找到详细的安装说明。

Docker Desktop for Windows 的安装及其详细要求在第一章*,创建容器*中已经涵盖。

要能够使用云托管的注册表,您将需要自己的 Azure 帐户。如果您之前没有为前几章创建帐户,您可以在此处了解如何获取用于个人使用的有限免费帐户:azure.microsoft.com/en-us/free/

您可以从本书的官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter03

存储和共享容器图像

到目前为止,您已经使用docker pull hello-world命令拉取了您的第一个hello-world容器,甚至使用了docker run hello-world命令。在图像拉取期间,底层会发生一些事情:

  1. Docker 引擎连接到所谓的 Docker 镜像注册表。注册表可以被明确指定,但默认情况下,这是官方的公共注册表,称为 Docker Hub(hub.docker.com/)。

  2. Docker 引擎对注册表进行身份验证,如果需要的话。如果您正在运行私有注册表或 Docker Hub 的付费计划,这可能是一个情况。

  3. 所选的hello-world图像被下载为一组由 SHA256 摘要标识的层。每个层在下载后都会被解压缩。

  4. 该图像被存储在本地。

如果执行docker run命令并且本地存储中不存在图像,则会发生类似的过程。第一次,它将被拉取,以后将使用本地缓存的图像。

如果您熟悉 GitHub 或其他源代码仓库托管,您会发现图像管理和图像注册表中有许多类似的概念。

因此,直观地说,图像注册表是一个有组织的、分层的系统,用于存储 Docker 图像。图像的层次结构包括以下级别:

  • 注册表:这是层次结构的顶层。

  • 存储库:注册表托管多个存储库,这些存储库是图像的存储单元。

  • 标签:单个图像的版本标签。存储库将由相同图像名称和不同标签标识的多个图像分组。

注册表中的每个图像都由图像名称和标签标识,并且上面的层次结构反映在最终图像名称中。使用以下方案:<registryAddress>/<userName>/<repositoryName>:<tag>,例如,localregistry:5000/ptylenda/test-application:1.0.0。在使用 Docker 命令时,其中一些部分是可选的,如果您不提供值,将使用默认值:

  • <registryAddress>是用于存储图像的注册表的 DNS 名称或 IP 地址(连同端口)。如果省略此部分,将使用默认的 Docker Hub 注册表(docker.io)。目前,没有办法更改注册表地址的默认值,因此,如果您想使用自定义注册表,必须始终提供此部分。

  • <userName>标识拥有该镜像的用户或组织。在 Docker Hub 的情况下,这是所谓的 Docker ID。是否需要这部分取决于注册表 - 对于 Docker Hub,如果您没有提供 Docker ID,它将假定官方镜像,这是一组由 Docker 维护和审核的 Docker 仓库。

  • <repositoryName>是您帐户中的唯一名称。镜像名称形成为<registryAddress>/<userName>/<repositoryName>

  • <tag>是给定镜像仓库中的唯一标签,用于组织镜像,大多数情况下使用版本控制方案,例如1.0.0。如果未提供此值,则将使用默认值latest。我们将在本章后面重点讨论标记和版本控制镜像。

使用多架构 Docker 镜像变体,可以在相同的镜像名称和标签下拥有不同的镜像,用于不同的架构。镜像的版本将根据运行 Docker 客户端的机器的架构自动选择。可以通过在镜像标签后面显式地使用额外的@sha256:<shaTag>部分来明确识别这样的镜像,例如,docker.io/adamparco/demo:latest@sha256:2b77acdfea5dc5baa489ffab2a0b4a387666d1d526490e31845eb64e3e73ed20。有关更多详细信息,请访问engineering.docker.com/2019/04/multi-arch-images/

现在您知道 Docker 镜像是如何标识的,让我们来看看如何将镜像推送到 Docker 注册表。

将镜像推送到 Docker 注册表

使用注册表共享容器镜像是通过镜像推送来完成的。这个过程将所需的镜像层上传到所选仓库的注册表中,并使其可以被其他具有对给定仓库访问权限的用户拉取。在我们将用于演示的 Docker Hub 的情况下,您的仓库将是公开的,除非您有付费计划。

将镜像推送到 Docker Hub 需要进行身份验证。如果您还没有在 Docker Hub 注册,请转到hub.docker.com/并按照那里的说明操作。注册后,您将需要您的 Docker ID 和密码才能使用docker login命令登录到服务中:

PS C:\WINDOWS\system32> docker login 
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: packtpubkubernetesonwindows
Password:
Login Succeeded

在本书中,我们将使用packtpubkubernetesonwindows Docker ID 来演示我们的示例。建议您创建自己的帐户,以便能够完全跟随本书中的示例。按照以下步骤:

  1. 第一步是创建一个实际可以推送到注册表的镜像。我们将使用以下 Dockerfile 来创建镜像:
FROM mcr.microsoft.com/windows/servercore/iis:windowsservercore-1903

WORKDIR /inetpub/wwwroot
RUN powershell -NoProfile -Command ; \
    Remove-Item -Recurse .\* ; \
    New-Item -Path .\index.html -ItemType File ; \
    Add-Content -Path .\index.html -Value \"This is an IIS demonstration!\"

此 Dockerfile 创建了一个 IIS Web 主机镜像,用于提供显示 This is an IIS demonstration!的极简网页。

  1. 将 Dockerfile 保存在当前目录中。要构建它,请发出以下docker build命令:
docker build -t <dockerId>/iis-demo .

请记住,为了能够将镜像推送到 Docker Hub,您必须在仓库名称中提供您的 Docker ID。

  1. 成功构建后,您就可以准备将镜像推送到注册表。这可以通过docker push命令来执行:
docker push <dockerId>/iis-demo

以下截图显示了上述命令的输出:

Docker 将镜像作为一组层进行推送,这也优化了推送过程,如果已知的层正在使用。此外,请注意,在基于 Windows 的镜像的情况下,您将看到一个跳过外部层的消息。原因是来自 Docker Hub 以外的注册表的任何层,例如Microsoft Container RegistryMCR),将不会被推送到 Docker Hub。

现在,您还可以转到 Docker Hub 网页并检查您的镜像详细信息 - 对于示例镜像,您可以在此处检查:cloud.docker.com/repository/docker/packtpubkubernetesonwindows/iis-demo/。任何有权访问您的仓库的用户现在都可以使用docker pull <dockerId>/iis-demo命令来使用您的镜像。

您已成功将第一个镜像推送到 Docker Hub!现在,让我们来看看如何将镜像推送到自定义镜像注册表。

使用自定义本地注册表

在选择图像存储时,您不仅限于使用默认的 Docker Hub。实际上,在大多数情况下,当您运行生产代码时,您可能希望使用本地托管的 Docker Registry,这是一个用于存储和分发 Docker 镜像的开源、高度可扩展的应用程序。您应该在以下情况下考虑这个解决方案:

  • 您希望在隔离网络中分发 Docker 镜像

  • 您需要严格控制图像存储和分发的位置

  • 您希望补充您的 CI/CD 工作流程,以实现更快速和更可扩展的图像交付。

有关部署 Docker 注册表的详细信息可以在官方文档中找到:docs.docker.com/registry/deploying/

对于 Kubernetes 部署,通常的做法是在 Kubernetes 集群旁边甚至内部托管自己的 Docker 注册表。有许多自动化可用于此用例,例如,用于在 Kubernetes 上部署注册表的官方 Helm 图表:github.com/helm/charts/tree/master/stable/docker-registry

为了使用自定义镜像注册表,您只需要在使用 pull 或 push 命令时在镜像名称中指定注册表地址(如果需要,还需要端口),例如,localregistry:5000/ptylenda/test-application:1.0.0,其中localregistry:5000是本地托管的 Docker 注册表的域名和端口。实际上,当您为演示 Windows IIS 应用程序拉取图像时,您已经使用了自定义 Docker 镜像注册表:mcr.microsoft.com/windows/servercore/iis:windowsservercore-1903mcr.microsoft.com注册表是 MCR,是 Microsoft 发布图像的官方注册表。其他公共注册表和 MCR 之间的主要区别在于,它与 Docker Hub 紧密集成,并利用其 UI 提供可浏览的图像目录。Docker 引擎能够使用任何公开 Docker 注册表 HTTP API(docs.docker.com/registry/spec/api/)的系统作为容器镜像注册表。

目前,不可能更改 Docker 引擎的默认容器镜像注册表。除非在镜像名称中指定注册表地址,否则目标注册表将始终假定为docker.io

除了托管自己的本地镜像注册表外,还有一些基于云的替代方案提供私有镜像注册表:

在下一节中,您将学习如何使用 Docker Hub 自动化 Docker 镜像构建,以及如何使用 ACR 托管自己的注册表。

使用云容器构建器

Docker Hub 提供的一个功能是自动构建自动构建)。这在持续集成和持续部署场景中特别有用,您希望确保对代码存储库的每次推送都会导致构建、发布和可能的部署。

目前,Docker Hub 不支持 Windows 镜像,但这很可能会在不久的将来发生变化。我们将在 Linux 镜像上演示此用法,但所有原则仍然相同。有关 Windows 容器云构建,请查看下一节关于 Azure 容器注册表。

要设置自动构建,请完成以下步骤:

  1. 创建一个 GitHub 存储库,其中包含您的应用程序代码,以及定义应用程序的 Docker 镜像的 Dockerfile。

  2. 创建一个 Docker Hub 存储库并添加一个自动构建触发器。此触发器也可以在创建存储库后添加。

  3. 自定义构建规则。

  4. 可选地,启用自动测试。这是 Docker Hub 提供的验证功能,您可以在其中定义测试套件,以便测试每个新的镜像推送。

让我们开始创建一个 GitHub 存储库!

创建 GitHub 存储库

如果您没有 GitHub 帐户,可以免费创建一个帐户github.com/join。在本例中,我们将在hands-on-kubernetes-on-windows组织中创建一个专用的公共存储库,名为nginx-demo-index。让我们开始吧:

  1. 转到github.com/,使用*+*符号创建一个新存储库:

托管组织并非必需;您可以只使用自己的个人命名空间。该仓库旨在仅包含应用程序源代码(在我们的情况下,只是一个静态的index.html网页)和构建图像所需的 Dockerfile,这与 Docker 开发的建议最佳实践一致。

  1. 在创建了仓库之后,我们可以推送一些图像的源代码。您可以在本书的 GitHub 仓库中找到我们用于托管使用 nginx 的静态网页的最简化 Docker 图像的源代码:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter03/02_nginx-demo-index

  2. 为了克隆新创建的仓库,在 PowerShell 中,转到您想要拥有该仓库的目录并使用git clone命令:

git clone https://github.com/<userName>/<repositoryName>.git
  1. 将所有必需的源文件复制到仓库中,并使用git push命令进行推送:
git add -A
git commit -am "Docker image source code"
git push -u origin master
  1. 此时,当您转到 GitHub 网页时,例如github.com/hands-on-kubernetes-on-windows/nginx-demo-index,您应该能够看到仓库中的文件:

下一步是创建实际的 Docker Hub 仓库并配置自动构建。让我们开始吧!

创建具有自动构建的 Docker Hub 仓库

将 Docker Hub 仓库与自动构建集成需要将您的 GitHub 帐户连接到您的 Docker Hub 帐户并创建仓库本身。让我们开始吧:

  1. 打开hub.docker.com/,转到帐户设置。在已连接帐户部分,单击 GitHub 提供程序的连接:

  1. 授权 Docker Hub Builder 访问您的仓库。此时,如果需要,您还可以授予对任何组织的访问权限。

  2. 连接帐户后,再次打开hub.docker.com/,单击创建仓库部分的*+*按钮:

  1. 填写所有必需的细节。在我们的情况下,我们的仓库名称将是packtpubkubernetesonwindows/nginx-demo-index

  2. 在构建设置中,选择 GitHub 图标,并选择您刚刚创建的 GitHub 存储库,如前面的屏幕截图所示。

  3. 通过单击“单击此处自定义构建设置”来检查构建设置,以了解默认配置是什么:

  1. 默认设置适用于我们的镜像,因为我们希望在将新代码推送到主分支时触发构建。应该在您的 GitHub 存储库的根目录中使用名为 Dockerfile 的 Dockerfile 来构建镜像。

  2. 单击“创建和构建”以保存并立即基于存储库中的当前代码开始构建。

  3. 在最近的构建中,您应该看到您的镜像的一个挂起构建:

  1. 几分钟后,构建应该完成,packtpubkubernetesonwindows/nginx-demo-index:latest镜像应该可用。您可以通过使用docker pull packtpubkubernetesonwindows/nginx-demo-index:latest命令来验证这一点。

现在,让我们看看如何通过提交新代码轻松触发 Docker 镜像构建。

触发 Docker Hub 自动构建

通过在上一节创建的自动构建设置中,触发新的 Docker 镜像构建就像提交新代码到您的 GitHub 存储库一样简单。为了做到这一点,您必须执行以下操作:

  1. 对 GitHub 上镜像的源代码进行更改;例如,修改index.html文件:
<!DOCTYPE html>
<html>
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <h1>Hello World from nginx container! This is a new version of image for autobuild.</h1>
    </body>
</html>
  1. 提交并推送代码更改:
git commit -am "Updated index.html"
git push -u origin master
  1. 在 Docker Hub 上的此镜像存储库的构建选项卡中,您几乎立即应该看到已触发新的镜像构建(源提交:github.com/hands-on-kubernetes-on-windows/nginx-demo-index/tree/5ee600041912cdba3c82da5331542f48701f0f28):

如果您的构建失败,您可以随时检查构建详细信息和构建日志选项卡中的 Docker 构建日志。

  1. 构建成功后,在您的 Windows 机器上运行一个新容器来验证您的镜像:
docker run -it --rm `
 -p 8080:80 `
 packtpubkubernetesonwindows/nginx-demo-index:latest
  1. 镜像将自动从 Docker Hub 存储库中拉取。在您的网络浏览器中导航至http://localhost:8080。您应该看到以下输出:

恭喜-您已成功创建并触发了 Docker Hub 上的 Docker 镜像自动构建!在下一节中,您将学习如何为基于 Windows 的图像使用 Azure 容器注册表创建类似的设置。

创建 Azure 容器注册表

Azure 容器注册表ACR)是 Azure Cloud 提供的完全托管的私有 Docker 注册表。在本节中,我们将使用 Azure CLI 创建 ACR 的新实例。您将学习如何实现与 Docker Hub 提供的类似的构建自动化,但具有构建 Windows 图像和使用私有注册表的可能性。

您可以在第二章*,管理容器中的状态中找到 Azure CLI 的详细安装说明。

要创建 Azure 容器注册表实例,请按照以下步骤进行:

  1. 确保您已经使用 PowerShell 中的az login命令登录到 Azure CLI。然后为 ACR 实例创建一个专用资源组。在本例中,我们将使用acr-resource-group资源组和westeurope作为 Azure 位置:
**az group create `**
 **--name acr-resource-group `**
 **--location westeurope** 

您还可以使用本书 GitHub 存储库中提供的 PowerShell 脚本:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter03/03_CreateAzureContainerRegistry.ps1。请记住提供全局唯一的 ACR 名称,以便能够创建实例。

  1. 接下来,使用全局唯一名称创建基本层 ACR 实例(为演示目的,我们提供了handsonkubernetesonwinregistry,但您必须提供自己的唯一名称,因为它将成为注册表的 DNS 名称的一部分):
az acr create `
 --resource-group acr-resource-group ` --name handsonkubernetesonwinregistry `
 --sku Basic

如果您对 Azure 容器注册表的其他服务层感兴趣,请参考官方文档:docs.microsoft.com/en-us/azure/container-registry/container-registry-skus

您将获得有关新创建的注册表的详细信息:

{
  "adminUserEnabled": false,
  "creationDate": "2019-08-18T21:20:53.081364+00:00",
  "id": "/subscriptions/cc9a8166-829e-401e-a004-76d1e3733b8e/resourceGroups/acr-resource-group/providers/Microsoft.ContainerRegistry/registries/handsonkubernetesonwinregistry",
  "location": "westeurope",
  "loginServer": "handsonkubernetesonwinregistry.azurecr.io",
  "name": "handsonkubernetesonwinregistry",
  "networkRuleSet": null,
  "provisioningState": "Succeeded",
  "resourceGroup": "acr-resource-group",
  "sku": {
    "name": "Basic",
    "tier": "Basic"
  },
  "status": null,
  "storageAccount": null,
  "tags": {},
  "type": "Microsoft.ContainerRegistry/registries"
}

最重要的信息是"loginServer": "handsonkubernetesonwinregistry.azurecr.io",这将用于推送和拉取 Docker 镜像。

  1. 最后,最后一步是登录到注册表,这样您就可以在 Docker CLI 中使用注册表:
az acr login `
 --name handsonkubernetesonwinregistry

有了 ACR 设置,我们准备在云环境中使用 ACR 构建 Docker 镜像。

使用 Azure 容器注册表构建 Docker 镜像

为了演示目的,我们将使用一个简单的 Windows IIS 映像,用于托管静态 HTML 网页。您可以在本书的 GitHub 存储库中找到 Docker 映像源:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter03/04_iis-demo-index。要在 ACR 中构建映像,请按照以下步骤进行:

  1. 使用图像源代码克隆存储库,并在 PowerShell 中导航至Chapter03/04_iis-demo-index目录。

  2. 执行az acr build命令以在云环境中开始 Docker 镜像构建(记得提供 Docker 构建上下文目录,在本例中用dot表示当前目录):

az acr build `
 --registry handsonkubernetesonwinregistry `
 --platform windows `
 --image iis-demo-index:latest .
  1. 使用az acr build命令启动 ACR 快速任务。这将上传 Docker 构建上下文到云端,并在远程运行构建过程。几分钟后,构建过程应该完成。您可以期望类似于本地docker build命令的输出。

  2. 现在,您可以通过在本地机器上运行容器并从 ACR 中拉取映像来验证映像。您需要使用注册表的完整 DNS 名称(在本例中,这是handsonkubernetesonwinregistry.azurecr.io):

docker run -it --rm `
 -p 8080:80 `
 handsonkubernetesonwinregistry.azurecr.io/iis-demo-index:latest
  1. 在 Web 浏览器中导航至http://localhost:8080,并验证容器是否按预期运行:

您已成功执行了 ACR 构建快速任务!现在,我们可以开始自动化 ACR 构建触发器,以类似于使用 Docker Hub 的方式对 GitHub 存储库代码推送进行操作。

Azure 容器注册表的自动构建

Azure 容器注册表提供了类似于 Docker Hub 的功能,用于在代码推送时自动化 Docker 镜像构建。管道是高度可定制的,可以支持同时构建多个容器映像,但在本例中,我们将专注于在 GitHub 存储库代码推送时自动化单个映像构建。

对于更高级的多步骤和多容器场景,请查看官方文档:docs.microsoft.com/en-us/azure/container-registry/container-registry-tutorial-multistep-task

集成 ACR 和 GitHub 可以按以下步骤执行:

  1. 创建一个新的 GitHub 存储库并推送 Docker 镜像源代码。在这个例子中,我们将使用来自github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter03/04_iis-demo-index的源代码,它将被推送到一个新的 GitHub 存储库,即github.com/hands-on-kubernetes-on-windows/iis-demo-index

  2. 生成 GitHub 个人访问令牌PAT)以便在 ACR 中访问存储库。转到github.com/settings/tokens/new

  3. 输入 PAT 描述并选择 repo:status 和 public_repo 范围(对于私有存储库,您需要使用完整的 repo 范围):

  1. 点击“生成令牌”按钮。

  2. 您将获得一个 PAT 值。将令牌复制到安全位置,因为您将需要它来设置集成。

  3. 现在,让我们创建一个名为iis-demo-index-task的 ACR 任务。当代码被推送到github.com/hands-on-kubernetes-on-windows/iis-demo-index时,这将自动触发。所需的参数类似于 Docker Hub 的构建配置:

az acr task create `
 --registry handsonkubernetesonwinregistry `
 --name iis-demo-index-task `
 --platform windows `
 --image "iis-demo-index:{{.Run.ID}}" `
 --context https://github.com/hands-on-kubernetes-on-windows/iis-demo-index `
 --branch master `
 --file Dockerfile `
 --git-access-token <gitHubPersonalAccessTokenValue>

如果您在使用 Azure CLI 时遇到az acr task create: 'utputformat' is not a valid value for '--output'. See 'az acr task create --help'.的错误,请确保您正确地转义/引用 PowerShell 中的花括号。

  1. 使用az acr task run命令测试您的 ACR 任务定义:
az acr task run `
   --registry handsonkubernetesonwinregistry `
   --name iis-demo-index-task
  1. 在 Docker 镜像的源代码中,引入一个更改并提交并将其推送到 GitHub 存储库。例如,修改静态文本,使其读取如下:
Hello World from IIS container! The image is provided by Azure Container Registry and automatically built by Azure Container Registry task.
  1. 检索 ACR 任务日志以验证任务是否确实被触发:
az acr task  logs --registry handsonkubernetesonwinregistry

您应该看到类似以下的输出,这表明推送触发了一个新的任务实例:

  1. 任务完成后,拉取带有 Run ID 标记的镜像(在本例中,这是 cb5)。您也可以使用latest标记,但这需要使用docker rmi命令删除本地缓存的镜像:
docker pull handsonkubernetesonwinregistry.azurecr.io/iis-demo-index:cb5
  1. 使用handsonkubernetesonwinregistry.azurecr.io/iis-demo-index:cb5镜像创建一个新的容器:
docker run -it --rm `
 -p 8080:80 `
 handsonkubernetesonwinregistry.azurecr.io/iis-demo-index:cb5
  1. 在 Web 浏览器中导航至http://localhost:8080,并验证容器是否按预期运行。还要验证静态 HTML 页面是否包含代码推送中引入的更改:

其他云服务提供商也提供类似的设置 Docker 镜像注册表和构建流水线的服务。如果您对 Google Cloud Platform 服务感兴趣,请查看 GCP Cloud Build:cloud.google.com/cloud-build/docs/quickstart-docker

您已成功使用 GitHub 和 Azure 容器注册表设置了 Docker 镜像构建流水线 - 祝贺!现在,我们将快速查看图像标记和版本控制的最佳实践。

图像标记和版本控制

Docker 镜像使用标签来提供存储库中相同镜像的不同版本 - 每个镜像标签对应于给定的 Docker 镜像 ID。通常在构建镜像时会指定 Docker 镜像的标签,但您也可以使用docker tag命令显式添加标签:

docker pull mcr.microsoft.com/dotnet/core/sdk
docker tag mcr.microsoft.com/dotnet/core/sdk:latest mydotnetsdk:v1
docker tag mcr.microsoft.com/dotnet/core/sdk:latest mydotnetsdk:v2

在此示例中,我们拉取了.NET Core SDK 的latest镜像标签(因为没有明确指定),然后在本地镜像缓存中使用mydotnetsdk:v1mydotnetsdk:v2标签标记了该镜像。现在,可以在本地机器上执行操作时使用这些标签,如下所示:

docker run -it --rm mydotnetsdk:v1

让我们来看看latest标签,在使用 Docker 时经常使用。

使用最新标签

默认情况下,Docker CLI 假定一个特殊的标签称为latest。这意味着如果您执行docker pull applicationimage命令或docker run -it applicationimage命令,或在 Dockerfile 中使用FROM applicationimage,将使用applicationimage:latest标签。同样,当您执行docker build -t applicationimage .时,生成的 Docker 镜像将被标记为latest标签,并且每次构建都将产生applicationimage:latest的新版本。

重要的是要理解latest的行为与任何其他 Docker 镜像标签一样。它可以被视为 Docker 在用户未提供标签时始终使用的默认值。这可能会导致一些混淆,具体如下:

  • 在图像构建期间,如果为图像指定了标签,最新标签将不会被添加。这意味着,如果您将applicationimage:v1推送到注册表,这并不意味着applicationimage:latest将被更新。您必须明确执行。

  • 当图像所有者将新的 Docker 镜像版本推送到存储库并再次标记为latest标签时,并不意味着您本地缓存的图像将在docker build期间被更新和使用。您必须告诉 Docker CLI 尝试使用docker build--pull参数来拉取图像的更新版本。

  • 在 Dockerfile 的FROM指令中使用latest标签可能导致在不同时间点构建不同的图像,这通常是不可取的。例如,您可能在latest指向 SDK 版本 2.2 时使用mcr.microsoft.com/dotnet/core/sdk图像构建您的图像,但几个月后,使用相同的 Dockerfile 构建将导致使用版本 3.0 作为基础。

一般最佳实践(Kubernetes 也是如此)是避免使用latest标签部署生产容器,并仅在开发场景和本地环境的便利性使用latest标签。同样,为了确保您的 Docker 图像可预测且自描述,应避免在 Dockerfile 中使用带有latest标签的基础图像,而是使用特定的标签。

语义化版本

为了有效地管理 Docker 图像的版本和标记,您可以使用语义化版本Semver)作为一般策略。这种版本方案在图像分发商中被广泛采用,并帮助消费者了解您的图像如何演变。

通常,语义版本建议使用三个数字(主要版本、次要版本和修订版本),用点分隔,<major>.<minor>.<patch>,根据需要递增每个数字。例如,2.1.5 表示图像的主要版本是 2,次要版本是 1,当前修订版本为 5。这些发布数字的含义和递增规则与非容器化应用程序的版本化类似。

  • 主要版本:如果您引入了破坏兼容性或其他破坏性更改的功能,则递增。

  • 次要版本:如果您引入的功能与先前版本完全兼容,则递增。消费者不需要升级应用程序的用法。

  • 补丁:如果要发布错误修复或补丁,则递增。

有关 Semver 作为一般概念的更多细节可以在这里找到:semver.org/

在构建/推送 Docker 图像时使用 Semver 的最佳实践可以总结如下:

  • 构建图像的新版本时,始终创建新的补丁标签(例如 2.1.5)。

  • 始终覆盖现有的主要和次要标签(例如,2 和 2.1)。

  • 永远不要覆盖补丁标签。这确保了希望使用特定版本应用程序的图像使用者可以确保随着时间的推移不会发生变化。

  • 始终覆盖现有的latest标签。

以下一组命令显示了构建和标记新版本applicationimage Docker 图像的示例:

# New build a new version of image and push latest tag
docker build -t applicationimage:latest .
docker push applicationimage:latest

# New major tag
docker tag applicationimage:latest applicationimage:2
docker push applicationimage:2

# New minor tag
docker tag applicationimage:latest registry:2.1
docker push applicationimage:2.1

# New patch tag
docker tag applicationimage:latest applicationimage:2.1.5
docker push applicationimage:2.1.5

还可以引入其他标签,以添加到构建系统 ID 或用于图像构建的 git 提交 SHA-1 哈希的相关性。

确保图像供应链的完整性

提供图像供应链的内容信任是管理 Docker 图像中最重要但经常被忽视的主题之一。在任何通过不受信任的媒介(如互联网)进行通信和数据传输的分布式系统中,提供内容信任的手段至关重要,即验证进入系统的数据的来源(发布者)和完整性。对于 Docker 来说,这对于推送和拉取图像(数据)尤为重要,这是由 Docker 引擎执行的。

Docker 生态系统描述了Docker 内容信任DCT)的概念,它提供了一种验证数据数字签名的方法,这些数据在 Docker 引擎和 Docker 注册表之间传输。此验证允许发布者对其图像进行签名,并允许消费者(Docker 引擎)验证签名,以确保图像的完整性和来源。

在 Docker CLI 中,可以使用docker trust命令对图像进行签名,该命令构建在 Docker Notary 之上。这是用于发布和管理受信任内容集合的工具。签署图像需要具有关联的 Notary 服务器的 Docker 注册表,例如 Docker Hub。

要了解有关私有 Azure 容器注册表的内容信任的更多信息,请参阅docs.microsoft.com/en-us/azure/container-registry/container-registry-content-trust

签署图像

例如,我们将对本章中已构建并推送到 Docker Hub 的一个 Docker 镜像进行签名,即packtpubkubernetesonwindows/iis-demo-index。要跟进,请在自己的镜像存储库<dockerId>/iis-demo-index上执行操作。签名可以通过以下步骤完成:

  1. 生成委托密钥对。在本地,可以使用以下命令执行:
docker trust key generate <pairName>
  1. 您将被要求为私钥输入密码。选择一个安全的密码并继续。私人委托密钥将默认存储在~/.docker/trust/private中(也在 Windows 上),公共委托密钥将保存在当前工作目录中。

  2. 将委托公钥添加到公证服务器(对于 Docker Hub,它是notary.docker.io)。加载密钥是针对特定镜像存储库执行的,在 Notary 中,它由全局唯一名称GUN)标识。对于 Docker Hub,它们的形式为docker.io/<dockerId>/<repository>。执行以下命令:

docker trust signer add --key <pairName>.pub <signerName> **docker.io/<dockerId>/<repository>**

**# For example**
**docker trust signer add --key packtpubkubernetesonwindows-key.pub packtpubkubernetesonwindows docker.io/packtpubkubernetesonwindows/iis-demo-index** 
  1. 如果您是第一次为存储库执行委托,系统将自动要求使用本地 Notary 规范根密钥进行初始化。

  2. 给镜像打上一个特定的标签,以便进行签名,如下所示:

docker tag packtpubkubernetesonwindows/iis-demo:latest packtpubkubernetesonwindows/iis-demo:1.0.1
  1. 使用私人委托密钥对新标签进行签名并将其推送到 Docker Hub,如下所示:
docker trust sign packtpubkubernetesonwindows/iis-demo:1.0.1
  1. 或者,这可以通过docker push执行,前提是您在推送之前在 PowerShell 中设置了DOCKER_CONTENT_TRUST环境变量:
$env:DOCKER_CONTENT_TRUST=1
docker tag packtpubkubernetesonwindows/iis-demo:latest packtpubkubernetesonwindows/iis-demo:1.0.2
docker push packtpubkubernetesonwindows/iis-demo:1.0.2
  1. 现在,您可以检查存储库的远程信任数据:
docker trust inspect --pretty docker.io/packtpubkubernetesonwindows/iis-demo:1.0.1

接下来,让我们尝试在客户端启用 DCT 运行容器。

为客户端启用 DCT

为了在使用 Docker CLI 进行pushbuildcreatepullrun时强制执行 DCT,您必须将DOCKER_CONTENT_TRUST环境变量设置为1。默认情况下,Docker 客户端禁用了 DCT。按照以下步骤:

  1. 在当前的 PowerShell 会话中设置DOCKER_CONTENT_TRUST环境变量:
$env:DOCKER_CONTENT_TRUST=1
  1. 使用刚刚创建的签名镜像运行一个新容器:
docker run -d --rm docker.io/packtpubkubernetesonwindows/iis-demo:1.0.1
  1. 您会注意到容器可以正常启动。现在,尝试使用未签名的latest标签创建一个新容器:
PS C:\src> docker run -d --rm docker.io/packtpubkubernetesonwindows/iis-demo:latest
C:\Program Files\Docker\Docker\Resources\bin\docker.exe: No valid trust data for latest.
See 'C:\Program Files\Docker\Docker\Resources\bin\docker.exe run --help'.

这个简短的场景展示了如何使用 DCT 来确保用于容器创建的镜像的完整性和来源。

摘要

在本章中,您了解了 Docker 生态系统如何提供基础设施来存储和共享容器映像,使用 Docker 注册表进行演示。使用公共 Docker Hub 和使用 Azure CLI 从头开始设置的私有 Azure 容器注册表演示了图像注册表和自动云构建的概念。您还了解了使用语义版本控制方案对图像进行标记和版本控制的最佳实践。最后,您还了解了如何使用Docker 内容信任DCT)确保图像的完整性。

在下一章中,我们将深入研究 Kubernetes 生态系统,以了解一些关键概念以及它们目前如何支持 Windows 容器。

问题

  1. Docker 注册表是什么,它与 Docker Hub 有何关系?

  2. 什么是图像标签?

  3. Docker Hub 的标准图像存储库命名方案是什么?

  4. Azure 容器注册表是什么,它与 Docker Hub 有何不同?

  5. 什么是latest标签,何时建议使用它?

  6. 如何使用语义版本控制对图像进行版本控制(标记)?

  7. 为什么要使用 Docker 内容信任?

您可以在本书的评估部分找到这些问题的答案。

进一步阅读

第二部分:理解 Kubernetes 基础知识

理解 Kubernetes 基础知识对于开发和部署容器应用程序至关重要。在这里,您将了解 Kubernetes 与容器管理的关系以及该平台的关键组件是什么。

本节包括以下章节:

  • [第四章],Kubernetes 概念和 Windows 支持

  • [第五章],Kubernetes 网络

  • [第六章],与 Kubernetes 集群交互

第四章:Kubernetes 概念和 Windows 支持

在之前的章节中,我们专注于 Windows 平台上的容器化和 Docker 支持。这些概念主要局限于单机场景,即应用程序只需要一个容器主机。对于生产级分布式容器系统,您必须考虑不同的方面,如可伸缩性、高可用性和负载平衡,这总是需要对运行在多个主机上的容器进行编排。

容器编排是在大型动态环境中管理容器生命周期的一种方式,从提供和部署容器到管理网络、提供容器的冗余和高可用性、自动扩展和缩减容器实例、自动健康检查和遥测收集。解决容器编排问题并不是一件简单的事情,这就是为什么 Kubernetes(简称 k8s,其中 8 代表省略的字符数)诞生的原因。

Kubernetes 的故事可以追溯到 21 世纪初的 Borg 系统,这是谷歌内部开发的用于大规模管理和调度作业的系统。随后,在 2010 年代初,谷歌开发了 Omega 集群管理系统,作为对 Borg 的全新重写。虽然 Omega 仍然只在谷歌内部使用,但在 2014 年,Kubernetes 作为开源容器编排解决方案宣布推出,它的根源来自 Borg 和 Omega。2015 年 7 月,Kubernetes 的 1.0 版本发布时,谷歌与 Linux 基金会合作成立了云原生计算基金会(CNCF)。该基金会旨在赋予组织能力,使它们能够在公共、私有和混合云等现代动态环境中构建和运行可扩展的应用程序。四年后的 2019 年 4 月,发布了 Kubernetes 1.14 版本,为 Windows 节点和 Windows 容器提供了生产级支持。本章主要讨论 Kubernetes 在 Windows 方面的当前状态!

云原生应用是容器编排中常用的术语,用于指代利用容器化、云计算框架和组件的松耦合(微服务)的应用程序。但这并不一定意味着云原生应用必须在云中运行 - 它们遵循一组原则,使它们易于在本地或公共/私有云中托管。如果您对了解更多关于 CNCF 感兴趣,请参考官方网页:www.cncf.io/

在本章中,我们将涵盖以下主题:

  • Kubernetes 高级架构

  • Kubernetes 对象

  • Windows 和 Kubernetes 生态系统

  • Windows 上的 Kubernetes 限制

  • 从头开始创建您自己的开发集群

  • 生产集群部署策略

  • 托管的 Kubernetes 提供程序

技术要求

本章,您将需要以下内容:

  • 已安装 Windows 10 Pro、企业版或教育版(1903 版或更高版本,64 位)

  • 已安装 Docker Desktop for Windows 2.0.0.3 或更高版本

  • 已安装的 Windows Chocolatey 软件包管理器(chocolatey.org/

  • 已安装 Azure CLI

如何安装 Docker Desktop for Windows 及其系统要求已在第一章*,Creating Containers*中介绍过。

使用 Chocolatey 软件包管理器并非强制,但它可以使安装和应用程序版本管理变得更加容易。安装过程在此处有文档记录:chocolatey.org/install

对于 Azure CLI,您可以在第二章*,*Managing State in Containers**中找到详细的安装说明。

要了解托管的 Kubernetes 提供程序,您将需要自己的 Azure 帐户,以便创建具有 Windows 节点的 AKS 实例。如果您之前没有为本书的前几章创建帐户,您可以在此处阅读有关如何获取个人使用的有限免费帐户的更多信息:azure.microsoft.com/en-us/free/

您可以从本书的官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter04

Kubernetes 高级架构

在本节和下一节中,我们将重点关注 Kubernetes 的高级架构及其核心组件。如果您已经对 Kubernetes 有一般了解,但想了解更多关于 Kubernetes 对 Windows 的支持,您可以跳到Windows 和 Kubernetes 生态系统部分。

Kubernetes 是什么?

总的来说,Kubernetes 可以被看作是以下内容:

  • 容器(微服务)编排系统

  • 用于运行分布式应用程序的集群管理系统

作为一个容器编排器,Kubernetes 解决了在大规模部署容器化的云原生应用时出现的常见挑战。这包括以下内容:

  • 在多个容器主机(节点)上进行容器的配置和部署

  • 服务发现和负载均衡网络流量

  • 自动扩展容器实例的规模

  • 自动化部署和回滚新的容器镜像版本

  • 自动的、最佳的容器资源(如 CPU 或内存)装箱

  • 应用程序监控、遥测收集和健康检查

  • 编排和抽象存储(本地、本地部署或云端)

与此同时,Kubernetes 也可以被描述为一个集群管理系统 - 主节点(或在高可用部署中的多个主节点)负责有效地协调处理实际容器工作负载的多个工作节点。这些工作负载不仅限于 Docker 容器 - Kubernetes 在工作节点上使用容器运行时接口(CRI)来抽象容器运行时。最终,集群客户端(例如 DevOps 工程师)可以使用主节点暴露的 RESTful API 来管理集群。集群管理使用声明式模型进行,这使得 Kubernetes 非常强大 - 您描述所需的状态,Kubernetes 会为了将集群的当前状态转换为所需的状态而进行所有繁重的工作。

使用临时命令进行命令式集群管理也是可能的,但通常不建议用于生产环境。操作是直接在活动集群上执行的,并且没有先前配置的历史记录。在本书中,我们将尽可能使用声明性对象配置技术。有关 Kubernetes 集群管理技术的更详细讨论,请参阅官方文档:kubernetes.io/docs/concepts/overview/working-with-objects/object-management/

Kubernetes 的高级架构可以在以下图表中看到。我们将在接下来的几段中逐个介绍每个组件:

让我们首先关注 Kubernetes 主节点,也称为控制平面的角色。

Kubernetes 主节点-控制平面

在 Kubernetes 集群中,主节点(控制平面)由一组负责全局决策的组件组成,例如将应用实例调度和部署到工作节点,以及管理集群事件。此外,主节点为工作节点和管理客户端之间的通信公开了 API。

主要组件不限于在专用主机上运行;它也可以在工作节点上运行。主节点可以像 Kubernetes 集群中的任何节点一样充当工作节点。但是,一般来说,由于可靠性原因,不建议这样做-而且对于生产环境,您应该考虑运行高可用性的 Kubernetes 设置,这需要多个主节点运行组件冗余。

运行 Kubernetes 主服务的最显著限制之一是它们必须托管在 Linux 机器上。不可能在 Windows 机器上拥有主组件,这意味着即使您计划仅运行 Windows 容器,您仍然需要 Linux 机器作为主机。目前,尚无计划实施仅限 Windows 的 Kubernetes 集群,尽管随着 Windows 子系统的开发进展,情况可能会发生变化。

我们将简要介绍组成主节点的组件。让我们首先看一下 Kubernetes API 服务器(或kube-apiserver,这是该组件的二进制名称)。

kube-apiserver

Kubernetes API 服务器kube-apiserver)是 Kubernetes 控制平面中的核心组件,充当客户端和集群组件之间所有交互的网关。其主要职责如下:

  • 公开作为一组通过 HTTPS 的 RESTful 端点实现的集群 API。API 由管理集群的客户端以及内部 Kubernetes 组件使用。Kubernetes 集群中的所有资源都被抽象为 Kubernetes API 对象。

  • etcd集群中持久化集群状态 - 客户端执行的每个操作或集群组件报告的状态更新都必须通过 API 服务器并持久化存储在集群中。

  • 用户和服务账户的认证和授权。

  • 请求的验证。

  • 提供watch API 以通知订阅者(例如其他集群组件)有关集群状态变化的增量通知源。观察 API 是使 Kubernetes 高度可扩展和分布式的关键概念。

在高可用的 Kubernetes 部署中,kube-apiserver托管在多个主节点上,位于专用负载均衡器后面。

etcd 集群

为了持久化集群状态,Kubernetes 使用etcd - 一个分布式、可靠的键值存储,利用 Raft 分布式一致性算法来提供顺序一致性。etcd集群是控制平面中最重要的部分 - 这是整个集群的真相来源,无论是当前状态还是集群的期望状态。

通常,仅建议用于测试目的的单节点etcd集群。对于生产场景,您应该始终考虑至少运行一个由五个成员组成的集群(成员数为奇数),以提供足够的容错能力。

在选择etcd集群部署拓扑时,可以考虑堆叠的 etcd 拓扑或外部的 etcd 拓扑。堆叠的 etcd 拓扑由每个 Kubernetes 主节点实例的一个 etcd 成员组成,而外部的 etcd 拓扑则利用了一个独立于 Kubernetes 部署的 etcd 集群,并通过负载均衡器可用。您可以在官方文档中了解更多关于这些拓扑的信息:kubernetes.io/docs/setup/production-environment/tools/kubeadm/ha-topology/

etcd公开的watch协议也是 Kubernetes 中 watch API 的核心功能,由kube-apiserver为其他组件提供。

kube-scheduler

Kubernetes Schedulerkube-scheduler)组件的主要责任是调度容器工作负载(Kubernetes Pods)并将它们分配给满足运行特定工作负载所需条件的健康工作节点。

Pod 是 Kubernetes 系统中最小的部署单元,是一个或多个具有共享网络和存储的容器组。我们将在下一节中介绍这个 Kubernetes 对象。

调度分为两个阶段:

  • 过滤

  • 评分

在过滤阶段,kube-scheduler确定能够运行给定 Pod 的节点集。这包括检查节点的实际状态,并验证 Pod 定义中指定的任何资源要求。在这一点上,如果没有节点可以运行给定的 Pod,那么 Pod 将无法调度并保持挂起状态。接下来,在评分步骤中,调度程序根据一组策略为每个节点分配分数。然后,调度程序将 Pod 分配给具有最高分数的节点。

您可以在官方文档中了解更多有关可用策略的信息:kubernetes.io/docs/concepts/scheduling/kube-scheduler/#kube-scheduler-implementation

Kubernetes 设计提供了很大的可扩展性和替换组件的可能性。Kube-scheduler 是用来演示这一原则的组件之一。即使其内部业务逻辑很复杂(所有高效的调度启发式算法都相当复杂...),调度程序只需要监视未分配的 Pod,确定最适合它们的节点,并通知 API 服务器进行分配。您可以在这里查看自定义调度程序的示例实现:banzaicloud.com/blog/k8s-custom-scheduler/

现在,让我们来看看kube-controller-manager

kube-controller-manager

Kubernetes Controller Managerkube-controller-manager)是负责在集群中运行核心协调和控制循环的组件。控制器管理器由一组独立的专门控制器组成。控制器的主要目的是观察 API 服务器公开的当前期望集群状态,并命令试图将当前状态转换为期望状态的变化。

kube-controller-manager二进制文件中提供的最重要的控制器如下:

  • Node Controller(以前称为 nodelifecycle):观察节点的状态,并在节点不可用时做出反应。

  • ReplicaSet Controller(replicaset):负责确保每个 ReplicaSet API 对象运行正确数量的 Pod。

  • Deployment Controller(deployment):负责管理关联的 ReplicaSet API 对象并执行部署和回滚。

  • Endpoints Controller(endpoint):管理 Endpoint API 对象。

  • Service Account Controller(serviceaccount)和 Token Controller(serviceaccount-token):负责为新命名空间创建默认帐户和访问令牌。

您可以将 kube-controller-manager 视为确保集群的当前状态朝向期望集群状态移动的 Kubernetes 大脑。

cloud-controller-manager

最初是kube-controller-manager的一部分,Kubernetes Cloud Controller Managercloud-controller-manager)提供特定于云的控制循环。分离云控制器管理器的原因是为了更容易地发展特定于云的连接器(提供商)代码,这些代码在大多数情况下以不同的节奏发布。

截至 Kubernetes 1.17,cloud-controller-manager 仍处于测试阶段。您可以在官方文档中检查该功能的当前状态:kubernetes.io/docs/tasks/administer-cluster/running-cloud-controller

启用云控制器管理器时,必须禁用 kube-controller-manager 中的特定于云的控制循环。然后,以下控制器将依赖于云提供商的实现:

  • Node Controller:用于确定节点的状态并检测节点是否已删除。

  • 路由控制器:需要提供者来设置网络路由。

  • 服务控制器:通过提供者管理负载均衡器。

  • 卷控制器:使用提供者管理存储卷。

作为 Kubernetes 的一部分提供的外部云提供者的列表不断发展,并且可以在官方文档(kubernetes.io/docs/concepts/cluster-administration/cloud-providers/)和 Kubernetes 的组织 GitHub 页面(github.com/kubernetes?q=cloud-provider-&type=&language=)上进行检查。

Kubernetes 节点 - 数据平面

在 Kubernetes 集群中,数据平面由负责运行主控安排的容器工作负载的节点(以前称为minions)组成。节点可以是物理裸金属机器或虚拟机器,这在设计集群时提供了灵活性。

以下图表总结了组成 Kubernetes 节点的架构和组件:

在 Windows 支持方面,所有节点组件都可以在 Windows 和 Linux 机器上运行。这意味着 Windows Kubernetes 节点在与 Linux 节点相同的方式下对主控可见,并且从这个角度来看,它们只在它们可以支持的容器类型上有所不同。

Kubernetes 节点的主要组件如下:

  • kubelet:主要的 Kubernetes 代理,负责确保容器工作负载(Pods)在节点上执行。

  • 容器运行时:负责管理容器的软件。它由容器运行时接口(CRI)抽象出来。

  • kube-proxy:负责管理本地节点网络的网络代理。

让我们先来看看kubelet

kubelet

在集群中的每个节点上运行,kubelet是一个负责确保控制平面分配的容器工作负载(Pods)得到执行的服务。此外,它还负责以下工作:

  • 报告节点和 Pods 状态给 API 服务器

  • 报告资源利用情况

  • 执行节点注册过程(加入新节点到集群时)

  • 执行活跃性和就绪性探针(健康检查)并将其状态报告给 API 服务器

为执行实际的与容器相关的操作,kubelet 使用容器运行时。

容器运行时

Kubelet 并不直接与 Docker 耦合 - 实际上,正如我们在本节介绍中提到的,Docker 并不是 Kubernetes 支持的唯一容器运行时。为了执行与容器相关的任务,例如拉取镜像或创建新容器,kubelet 利用容器运行时接口(CRI),这是一个为不同运行时抽象所有常见容器操作的插件接口。

容器运行时接口的实际定义是一个 protobuf API 规范,可以在官方存储库中找到:github.com/kubernetes/cri-api/。任何实现此规范的容器运行时都可以用于在 Kubernetes 中执行容器工作负载。

目前,在 Linux 上可以与 Kubernetes 一起使用的容器运行时有很多。最流行的如下:

  • Docker:由dockershim抽象出的传统 Docker 运行时,这是kubelet的 CRI 实现。

  • CRI-containerd:简而言之,containerd是 Docker 的一个组件,负责容器的管理。目前,CRI-containerd 是 Linux 上 Kubernetes 的推荐运行时。更多信息,请访问containerd.io/

  • CRI-O:专门用于 CRI 的容器运行时实现,遵循Open Containers Initiative(OCI)规范。更多信息,请访问cri-o.io/

  • gVisor:与 Docker 和 containerd 集成的符合 OCI 标准的容器沙箱运行时。更多信息,请访问gvisor.dev/

dockershim 和 CRI-containerd 之间的区别可以在下图中看到:

CRI-containerd 运行时提供了一个更简单的架构,守护程序和进程之间的通信更少,从而消除了传统的 Docker 引擎。这个解决方案旨在提供一个裁剪版的 Docker 运行时,暴露出关键的组件供 Kubernetes 使用。

如果您对 Docker 和 containerd 分离的历史背景感兴趣,可以阅读以下文章:alexander.holbreich.org/docker-components-explained/

对于 Windows,支持的列表要短得多,目前包括 Docker(企业版 18.09+,也由 dockershim 抽象)和即将支持的 CRI-containerd。预计当 containerd 1.3 的稳定版本发布并且 runhcs shim 得到全面支持时,这将可用。这还将带来对容器的 Hyper-V 隔离的新支持,目前(截至 Kubernetes 1.17)作为有限的实验性功能实现,没有使用 CRI-containerd。

kube-proxy

在 Kubernetes 集群中,节点上的网络规则和路由由运行在每个节点上的 kube-proxy 管理。这些规则允许 Pod 与外部客户端之间进行通信,并且是 Service API 对象的重要组成部分。在 Linux 平台上,kube-proxy 使用 iptables 配置规则(最常见),而在 Windows 平台上,使用 Host Networking Service (HNS)。

我们将在下一章更详细地介绍 Kubernetes 网络。

DNS

内部 DNS 服务器是可选的,并且可以作为附加组件安装,但在标准部署中强烈建议使用,因为它简化了服务发现和网络。目前,Kubernetes 使用的默认 DNS 服务器是 CoreDNS(coredns.io/)。

Kubernetes 会自动为每个容器的域名解析配置添加一个内部静态 IP 地址的 DNS 服务器。这意味着在 Pod 中运行的进程可以通过知道它们的域名与集群中运行的服务和 Pod 进行通信,这些域名将解析为实际的内部 IP 地址。Kubernetes Service 对象的概念将在下一节中介绍。

现在,让我们来看一下最常用的 Kubernetes 对象。

Kubernetes 对象

在本书的后面部分将介绍在 Windows 节点上设置 Kubernetes 集群的复杂性,并且将在 Linux 示例上演示原则。从 Kubernetes API 服务器的角度来看,Windows 和 Linux 节点的操作方式几乎相同。

在 Kubernetes 集群中,集群状态由 kube-apiserver 组件管理,并持久存储在etcd集群中。状态被抽象和建模为一组 Kubernetes 对象 - 这些实体描述了应该运行什么容器化应用程序,它们应该如何被调度,以及关于重新启动或扩展它们的策略。如果您想在 Kubernetes 集群中实现任何目标,那么您必须创建或更新 Kubernetes 对象。这种模型称为声明性模型 - 您声明您的意图,Kubernetes 负责将集群的当前状态更改为期望的(预期的)状态。声明性模型和保持期望状态的理念是使 Kubernetes 如此强大和易于使用的原因。

在本书中,我们将遵循官方文档的惯例,其中对象是大写的;例如,Pod 或 Service。

每个 Kubernetes 对象的解剖结构完全相同;它有两个字段:

  • Spec:这定义了对象的期望状态。这是您在创建或更新对象时定义要求的地方。

  • Status:这是由 Kubernetes 提供的,并描述了对象的当前状态

始终需要使用 Kubernetes API 来处理 Kubernetes 对象。最常见的情况是使用 Kubernetes 的命令行接口(CLI)来管理 Kubernetes 对象,该接口以kubectl二进制文件的形式提供。还可以使用客户端库直接与 Kubernetes API 进行交互。

kubectl的安装和其用法示例将在第六章中进行介绍,与 Kubernetes 集群交互

现在,让我们快速看一下示例 Kubernetes 对象的结构。当直接与 Kubernetes API 交互时,对象必须以 JSON 格式指定。然而,kubectl允许我们使用 YAML 清单文件,在执行操作时将其转换为 JSON。通常建议使用 YAML 清单文件,并且您可以期望在文档中找到的大多数示例都遵循这个惯例。例如,我们将使用一个包含单个 nginx web 服务器 Linux 容器定义的 Pod 的定义,存储在名为nginx.yaml的文件中。

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-example
  labels:
    app: nginx-host
spec:
  containers:
  - name: nginx
    image: nginx:1.17
    ports:
    - containerPort: 80

清单文件中的必需部分如下:

  • apiVersion:用于此对象的 Kubernetes API 的版本。

  • kind:Kubernetes 对象的类型。在这种情况下,这是Pod

  • metadata:对象的附加元数据。

  • spec:对象规范。在示例规范中,nginx 容器使用nginx:1.17 Docker 镜像并暴露端口80。每个 Kubernetes 对象的规范都不同,并且必须遵循 API 文档。例如,对于 Pod,您可以在这里找到 API 参考:kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#podspec-v1-core

现在创建 Pod 就像运行以下kubectl apply命令一样简单:

kubectl apply -f nginx.yaml

如果您想尝试此命令而没有本地 Kubernetes 集群,我们建议使用 Kubernetes playground 中的一个;例如,www.katacoda.com/courses/kubernetes/playground

  1. 在主窗口中运行以下kubectl命令,它将应用托管在 GitHub 上的清单文件:
kubectl apply -f https://raw.githubusercontent.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/master/Chapter04/01_pod-example/nginx.yaml
  1. 几秒钟后,Pod 将被创建,其STATUS应为Running
master $ kubectl get pod -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE   READINESS GATES
nginx-pod-example   1/1     Running   0          15s   10.40.0.1   node01   <none>           <none>
  1. 在主窗口中使用curl命令获取 Pod 的 IP(在本例中为10.40.0.1)以验证容器是否确实在运行。您应该看到默认 nginx 网页的原始内容:
curl http://10.40.0.1:80

kubectl目前提供了两种声明性方法来管理 Kubernetes 对象:清单文件和 kustomization 文件。使用 kustomize 方法更加强大,因为它以可预测的结构组织清单文件和配置生成。您可以在这里了解更多关于 kustomize 的信息:github.com/kubernetes-sigs/kustomize/tree/master/docs

现在,让我们更仔细地看一下 Pod API 对象。

Pods

Kubernetes 使用 Pod 作为部署和扩展的基本原子单位,并代表集群中运行的进程 - 从 Microsoft Hyper-V 的类比来说,就像是在 Hyper-V 集群中部署的单个虚拟机。Kubernetes Pod 由一个或多个共享内核命名空间、IPC、网络堆栈(您可以通过相同的集群 IP 地址对其进行寻址,并且它们可以通过 localhost 进行通信)和存储的容器组成。要理解 Pod,了解名称的起源是很有帮助的:在英语中,pod 是一群鲸鱼,而 Docker 使用鲸鱼作为其标志 - 将鲸鱼想象成 Docker 容器!

在最简单的形式中,你可以创建单容器 Pod - 这就是我们在本节介绍中演示 nginx Pod 创建时所做的。对于某些情况,你可能需要多容器 Pod,其中主容器伴随着其他容器,用于多种目的。让我们来看看其中一些:

  • 辅助 容器,可以执行各种辅助操作,比如日志收集,为主容器进行数据同步等。

  • 适配器 容器,可以规范输出或监视主容器的数据,以便其他服务可以使用。

  • 大使 容器,代理主容器与外部世界的通信。

  • 初始化 容器,这些是在 Pod 中的应用容器之前运行的专门容器。例如,它们可以设置环境,这在主容器镜像中没有执行。

从技术上讲,即使是单容器 Pod 也包含一个额外的基础设施容器,通常是一个暂停镜像。它充当了 Pod 中所有容器的容器,并启用了内核命名空间共享。如果你对基础设施容器的更多细节感兴趣,请参考这篇文章:www.ianlewis.org/en/almighty-pause-container

Pod 的概念可以在以下图表中看到:

在使用 Pod 时,有一些考虑事项需要牢记在心:

  • Pod 的容器始终在一个节点上运行,一旦 Pod 被创建,它就始终绑定到一个节点上。

  • 通过增加更多的 Pod 来扩展你的应用,而不是在同一个 Pod 中增加更多的容器。

  • Pod 被认为是就绪并且能够响应请求时,所有它的容器都是就绪的。容器的状态由探针来确定,例如存活和就绪探针,这些可以在规范中定义。

  • Pod 是短暂的。它们被创建,它们死亡,如果需要的话,新的 Pod 会被重新创建。

  • 当 Pod 被重新创建时,它会获得一个新的集群 IP。这意味着你的应用设计不应该依赖静态 IP 分配,并且假设 Pod 甚至可能在不同的节点上重新创建。

你很少会像我们在本节介绍中那样独立创建裸 Pod。在大多数情况下,它们是通过部署进行管理的。

Pod 具有有限的生命周期,如果容器内部崩溃或退出,根据重启策略,它们可能不会自动重新创建。为了在集群中保持一定数量的具有特定 Spec 和元数据的 Pod,您需要ReplicaSet对象。

副本集

Kubernetes 在 Pod 的基础上构建了许多强大的概念,使容器管理变得简单和可预测。最简单的概念是ReplicaSetAPI 对象(ReplicationController 的后继者),其目的是维护一定数量的健康 Pod(副本)以满足特定条件。换句话说,如果您说“我希望在我的集群中运行三个 nginx Pod”,ReplicaSet 会为您完成。如果一个 Pod 被销毁,ReplicaSet将自动创建一个新的 Pod 副本以恢复所需状态。

让我们看一个示例 ReplicaSet 清单nginx-replicaset.yaml文件,创建三个 nginx Pod 的副本:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-replicaset-example
spec:
  replicas: 3
  selector:
    matchLabels:
      environment: test
  template:
    metadata:
      labels:
        environment: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.17
        ports:
        - containerPort: 80

ReplicaSet Spec 有三个主要组件:

  • replicas:定义应使用给定的template和匹配的selector运行的 Pod 副本的数量。为了保持所需的数量,可能会创建或删除 Pod。

  • selector:标签选择器,定义了如何识别 ReplicaSet 将获取的 Pod。请注意,这可能会导致ReplicaSet获取现有的裸 Pod!

  • template:定义 Pod 创建的模板。元数据中使用的标签必须与selector正向匹配。

您可以以类似的方式应用ReplicaSet清单,就像我们在 Katacoda 游乐场中应用 Pod 一样:

kubectl apply -f https://raw.githubusercontent.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/master/Chapter04/02_replicaset-example/nginx-replicaset.yaml

您可以使用以下命令观察如何创建三个 Pod 副本:

kubectl get pod -o wide -w

ReplicaSets 通过将新创建或获取的 Pod 分配给 Pod 的.metadata.ownerReferences属性来标记它们自己(如果您感兴趣,可以使用kubectl get pod <podId> -o yaml命令进行检查)。这意味着,如果您创建完全相同的 ReplicaSet,具有完全相同的选择器但名称不同,例如nginx-replicaset-example2,它们不会窃取彼此的 Pod。但是,如果您已经创建了具有匹配标签的裸 Pod,例如environment: test,ReplicaSet 将获取它们,甚至可能删除 Pod,如果副本的数量太高!

如果您真的需要在 Kubernetes 集群中创建单个 Pod,最好使用ReplicaSet,将replicas字段设置为 1,这将充当容器的监督者。通过这种方式,您将防止创建没有所有者且仅与原始节点绑定的裸 Pods。

这可以在以下图表中看到:

通常,您不会自行创建 ReplicaSets,因为它们无法轻松执行滚动更新或回滚到早期版本。为了促进这种情况,Kubernetes 提供了建立在 ReplicaSets 之上的对象:部署和 StatefulSet。让我们先看一下部署。

部署

在这一点上,您已经知道了 Pods 和 ReplicaSets 的目的。部署是 Kubernetes 对象,为 Pods 和 ReplicaSets 提供声明性更新。您可以使用它们来声明性地执行以下操作:

  • 执行新的 ReplicaSet 的滚动

  • 更改 Pod 模板并执行受控滚动。旧的 ReplicaSet 将逐渐缩减,而新的 ReplicaSet 将以相同的速度扩展。

  • 执行回滚到部署的早期版本。

  • 扩展 ReplicaSet 的规模。

部署与 ReplicaSets 和 Pods 的关系可以在以下图表中看到:

您应该避免自行管理由部署创建的 ReplicaSets。如果需要对 ReplicaSet 进行任何更改,请在拥有的部署对象上执行更改。

请注意,由部署管理的 ReplicaSets 的意外获取 Pods 的问题不存在。原因是 Pods 和 ReplicaSets 使用一个特殊的、自动生成的标签,称为pod-template-hash,确保选择的唯一性。

让我们看一个示例部署清单,在nginx-deployment.yaml文件中:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment-example
spec:
  replicas: 3
  selector:
    matchLabels:
      environment: test
  template:
    metadata:
      labels:
        environment: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.17
        ports:
        - containerPort: 80

如您所见,基本结构与ReplicaSet几乎相同,但在执行声明性更新时,部署的行为有显著的差异。让我们在示例中快速演示一下:

  1. 手动创建部署清单文件,或使用wget命令下载它:
wget https://raw.githubusercontent.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/master/Chapter04/03_deployment-example/nginx-deployment.yaml
  1. 使用以下命令应用部署清单文件:
kubectl apply -f nginx-deployment.yaml --record

--record标志将kubernetes.io/change-cause的元数据注释添加到之前命令创建或修改的 API 对象中。此功能允许您轻松跟踪集群中的更改。

  1. 等待部署完全完成(您可以使用kubectl get deployment -w观察部署中就绪的 Pod 数量)。

  2. 现在,在 YAML 清单中的模板中更改 Pod 规范;例如,将.spec.template.spec.containers[0].image更改为nginx:1.**16**,然后再次应用部署清单。

  3. 接着,使用以下命令观察部署的进展:

master $ kubectl rollout status deployment nginx-deployment-example
Waiting for deployment "nginx-deployment-example" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment-example" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment-example" rollout to finish: 1 old replicas are pending termination...
deployment "nginx-deployment-example" successfully rolled out

部署的规范比 ReplicaSet 丰富得多。您可以查看官方文档以获取更多详细信息:kubernetes.io/docs/concepts/workloads/controllers/deployment/#writing-a-deployment-spec。官方文档包含了部署的多个用例,所有这些用例都有详细描述:kubernetes.io/docs/concepts/workloads/controllers/deployment/#use-case

正如您所看到的,对部署模板定义的声明性更新导致新 Pod 副本的平稳部署。旧的 ReplicaSet 被缩减,同时,一个新的具有新 Pod 模板的 ReplicaSet 被创建并逐渐扩展。现在,您可以尝试对现有的裸 ReplicaSet 执行相同的操作进行image更新,然后您会发现...实际上,什么都没有发生。这是因为 ReplicaSet 只使用 Pod 模板来创建新的 Pod。现有的 Pod 不会因此更改而被更新或删除。

只有当对部署的.spec.template进行更改时,才会触发部署。对部署清单的其他更改不会触发部署。

接下来,让我们看一个与部署类似的概念:StatefulSets。

StatefulSets

部署通常用于部署应用程序的无状态组件。对于有状态的组件,Kubernetes 提供了另一个名为StatefulSet的 API 对象。这种操作的原则与部署非常相似-它以声明方式管理 ReplicaSets 和 Pod,并提供平稳的部署和回滚。然而,也有一些关键区别:

  • StatefulSets 确保 Pod 具有确定性(粘性)ID,由<statefulSetName>-<ordinal>组成。对于部署,您将具有由<deploymentName>-<randomHash>组成的随机 ID。

  • 对于 StatefulSets,Pod 将按特定的可预测顺序启动和终止,同时扩展 ReplicaSet。

  • 在存储方面,Kubernetes 基于 StatefulSet 对象的volumeClaimTemplates为 StatefulSet 中的每个 Pod 创建 PersistentVolumeClaims,并始终将其附加到具有相同 ID 的 Pod。对于部署,如果选择使用volumeClaimTemplates,Kubernetes 将创建一个单一的 PersistentVolumeClaim,并将其附加到部署中的所有 Pod。

  • 您需要创建一个负责管理 Pod 的确定性网络标识(DNS 名称)的无头 Service 对象。无头 Service 允许我们将所有 Pod IP 作为 DNS A 记录返回到 Service 后面,而不是使用 Service Cluster IP 返回单个 DNS A 记录。

StatefulSets 使用与部署类似的 Spec-您可以通过查看官方文档了解有关 StatefulSets 的更多信息:kubernetes.io/docs/concepts/workloads/controllers/statefulset/

DaemonSets

DaemonSet 是另一个由控制器支持的对象,类似于 ReplicaSet,但旨在在集群中的每个节点上运行确切一个模板化的 Pod 副本(可选匹配选择器)。运行 DaemonSet 的最常见用例如下:

  • 管理给定集群节点的监控遥测,例如运行 Prometheus Node Exporter

  • 在每个节点上运行日志收集守护程序,例如fluentdlogstash

  • 运行故障排除 Pod,例如 node-problem-detector(github.com/kubernetes/node-problem-detector

在您的集群中可能会默认运行的 DaemonSets 之一是kube-proxy。在由 kubeadm 执行的标准集群部署中,kube-proxy作为 DaemonSet 分发到节点。您还可以在 Katacoda playground 上验证这一点:

master $ kubectl get daemonset --all-namespaces
NAMESPACE     NAME         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
kube-system   kube-proxy   2         2         2       2            2           <none>          12m
kube-system   weave-net    2         2         2       2            2           <none>          12m

如果您想了解有关 DaemonSets 的更多信息,请参阅官方文档:kubernetes.io/docs/concepts/workloads/controllers/daemonset/

服务

由 ReplicaSets 或 Deployments 创建的 Pods 具有有限的生命周期。在某个时候,您可以期望它们被终止,并且将创建新的 Pod 副本,具有新的 IP 地址。因此,如果您有一个运行 web 服务器 Pods 的 Deployment,需要与作为另一个 Deployment 一部分创建的 Pods 进行通信,例如后端 Pods,那么该怎么办呢?Web 服务器 Pods 不能假设任何关于后端 Pods 的 IP 地址或 DNS 名称的信息,因为它们可能随时间而改变。这个问题通过 Service API 对象得到解决,它为一组 Pods 提供可靠的网络连接。

通常,Services 针对一组 Pods,这是由标签选择器确定的。最常见的情况是通过使用完全相同的标签选择器为现有 Deployment 公开一个 Service。Service 负责提供可靠的 DNS 名称和 IP 地址,以及监视选择器结果并更新相关的 Endpoint 对象,其中包含匹配 Pods 的当前 IP 地址。

对于内部客户端(集群中的 Pods),到 Service 后面的 Pods 的通信是透明的 - 他们使用 Service 的 Cluster IP 或 DNS 名称,流量被路由到其中一个目标 Pods。路由能力由 kube-proxy 提供,但重要的是要知道流量不会通过任何主组件 - kube-proxy 在操作系统内核级别实现路由,并直接将其路由到适当的 Pod 的 IP 地址。在其最简单的形式中,目标 Pod 将被随机选择,但使用 IP Virtual Server (IPVS) 代理模式,您可以有更复杂的策略,例如最少连接或最短预期延迟。

Services 也可以将 Pods 暴露给外部流量。

Service 的工作原理可以在以下图表中看到:

让我们为我们的 nginx Deployment 公开一个示例 Service:

  1. 如果您在 Katacoda 游乐场上没有正在运行的 Deployment,可以使用以下命令创建一个:
kubectl apply -f https://raw.githubusercontent.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/master/Chapter04/03_deployment-example/nginx-deployment.yaml --record
  1. 使用以下 kubectl expose 命令为一个 Deployment 公开 Service:
kubectl expose deployment nginx-deployment-example
  1. 这个命令是命令式的,应该避免使用,而应该使用声明式的清单。这个命令相当于应用以下 Service 清单:
apiVersion: v1
kind: Service
metadata:
  name: nginx-deployment-example
spec:
  selector:
    environment: test
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  1. 现在,在 Service 被公开后,创建一个交互式的 busybox Pod,并启动 Bourne shell 进程:
kubectl run --generator=run-pod/v1 -i --tty busybox --image=busybox --rm --restart=Never -- sh
  1. 当容器外壳提示出现时,使用nginx-deployment-example服务名称作为 DNS 名称下载由 nginx Pods 提供的默认网页:
wget http://nginx-deployment-example && cat index.html

您还可以使用完全限定域名FQDN),其格式如下:<serviceName>.<namespaceName>.svc.<clusterDomain>。在这种情况下,它是nginx-deployment-example.default.svc.cluster.local

接下来,让我们快速看一下在 Kubernetes 中提供存储的对象。

与存储相关的对象

在本书中,我们只在需要时涵盖 Kubernetes 存储,因为这是一个广泛且复杂的主题-事实上,存储和管理任何集群的有状态组件通常是最难解决的挑战。如果您对 Kubernetes 中的存储细节感兴趣,请参考官方文档:kubernetes.io/docs/concepts/storage/

在 Docker 中,我们使用卷来提供持久性,可以是本地磁盘,也可以是远程/云存储,使用卷插件。 Docker 卷有一个独立于消耗它们的容器的生命周期。在 Kubernetes 中,有一个类似的概念,叫做 Volume,它与 Pod 紧密耦合,并且与 Pod 具有相同的生命周期。在 Kubernetes 中,Volume 的最重要的方面是它们支持多个后备存储提供者(类型)-这是由 Volume 插件和最近的容器存储接口CSI)抽象出来的,这是一个用于独立于 Kubernetes 核心开发的外部 Volume 插件的接口。例如,您可以将 Amazon Web Services EBS 卷或 Microsoft Azure Files SMB 共享挂载为 Pod 的 Volume-完整的 Volume 类型列表在这里:kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes

Volume 类型之一是PersistentVolumeClaimPVC),它旨在将 Pod 与实际存储解耦。PersistentVolumeClaim 是一个 API 对象,用于模拟对特定类型、类或大小存储的请求-可以将其视为说“我想要 10GB 的读/写一次 SSD 存储”。为了满足这样的请求,需要一个PersistentVolumePV)API 对象,这是集群自动化过程提供的一部分存储。PersistentVolume 类型也以类似于 Volume 的插件方式实现。

现在,持久卷的整个配置过程可以是动态的 - 它需要创建一个 StorageClass(SC)API 对象,并在定义 PVC 时使用它。创建新的 StorageClass 时,您提供一个具有特定参数的供应商(或插件),并且使用给定 SC 的每个 PVC 将自动创建一个 PV。

这些依赖关系可以在以下图表中看到:

当您想要部署一个挂载了 PersistentVolume 的 Pod 时,事件的顺序将如下:

  1. 创建一个带有所需供应商的 StorageClass。

  2. 创建一个使用 SC 的 PersistentVolumeClaim。

  3. PersistentVolume 是动态配置的。

  4. 在创建 Pod 时,将 PVC 挂载为一个 Volume。

动态配置的 PersistentVolumes 的概念得到了 StatefulSets 的补充。StatefulSets 定义了 volumeClaimTemplates,可以用于动态创建给定 StorageClass 的 PersistentVolumeClaims。通过这样做,整个存储配置过程是完全动态的 - 您只需创建一个 StatefulSet,底层存储对象就由 StatefulSet 控制器管理。您可以在这里找到更多详细信息和示例:kubernetes.io/docs/concepts/workloads/controllers/statefulset/#stable-storage

Kubernetes 中的这种存储架构确保了工作负载的可移植性,这意味着您可以轻松地将部署和 PersistentVolumeClaims 移动到不同的集群。您所需要做的就是提供一个满足 PVC 要求的 StorageClass。无需对 StatefulSet 或 PVC 进行修改。

Windows 和 Kubernetes 生态系统

最初,Kubernetes 是一个以 Linux 为中心的解决方案 - 这是因为主流的容器化也起源于 Linux 平台。2014 年,微软和 Windows 很快就加入了容器化世界 - 微软宣布将在即将发布的 Windows Server 2016 中支持 Docker Engine。Windows 的 Kubernetes 特别兴趣小组(SIG)于 2016 年 3 月启动,2018 年 1 月,Kubernetes 1.9 为 Windows Server 容器提供了 beta 支持。这种支持最终在 2019 年 4 月 Kubernetes 1.14 发布时成熟到生产级别。

为什么 Windows 对 Kubernetes 的支持如此重要?Windows 在企业工作负载中占据主导地位,而 Kubernetes 作为容器编排的事实标准,对 Windows 的支持带来了将绝大多数企业软件迁移到容器的可能性。开发人员和系统运营商现在可以利用相同的工具和流水线来部署 Windows 和 Linux 工作负载,以类似的方式扩展它们,并有效地监视它们。从商业角度来看,Windows 的容器采用意味着比普通虚拟机更好的运营成本和更好的硬件利用率。

Kubernetes 中的 Windows 容器支持不断发展,越来越多的限制正在被新功能取代。总的来说,有两个关键点需要记住:

  • 目前,Windows 机器只能作为节点加入集群。没有可能性,也没有计划在 Windows 上运行主控组件。同时运行 Linux 和 Windows 节点的集群被称为混合或异构。

  • 您需要最新稳定版本的 Kubernetes 和最新(或几乎最新)版本的 Windows Server 操作系统才能享受到提供的全面支持。例如,对于 Kubernetes 1.17,您需要 Windows Server 1809(半年频道发布)或 Windows Server 2019(来自长期服务频道的相同发布),尽管最新的 Windows Server 1903 也受支持。

目前,关于 Kubernetes 对 Windows 的支持的文档数量有限,但正在增长。最好的资源如下:

让我们来看一下 Kubernetes 对 Windows 的支持的当前状态以及截至 1.17 版本的限制。

Windows 上的 Kubernetes 限制

Windows Server 容器支持存在一系列限制,随着每个新版本的 Kubernetes 发布和 Windows Server 的新版本的到来,这些限制不断变化。一般来说,从 Kubernetes API 服务器和 kubelet 的角度来看,在异构(混合)Linux/Windows Kubernetes 集群中,Windows 上的容器的行为几乎与 Linux 容器相同。但是,细节上存在一些关键的差异。首先,让我们来看一些高层次的主要限制:

  • Windows 机器只能作为 worker 节点加入集群。在 Windows 上运行 master 组件的可能性和计划都不存在。

  • Worker 节点的操作系统的最低要求是 Windows Server 1809 或 2019。不能使用 Windows 10 机器作为节点。

  • 需要 Docker Enterprise Edition(基本版)18.09 或更高版本作为容器运行时。企业版对 Windows Server 操作系统免费提供。

  • Windows Server 操作系统需要许可证(www.microsoft.com/en-us/cloud-platform/windows-server-pricing)。Windows 容器镜像需要遵守微软软件补充许可证(docs.microsoft.com/en-us/virtualization/windowscontainers/images-eula)。对于开发和评估目的,你也可以使用评估中心:www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2019

  • 运行在 Kubernetes 上的 Windows Server 容器的 Hyper-V 隔离处于实验阶段(alpha),当前的设计将被 containerd 实现的运行时所取代。在那之前,进程隔离容器的兼容性规则适用 - 你必须使用与主机操作系统版本匹配的基本操作系统镜像运行容器。你可以在 第一章 创建容器 中找到更多细节。

  • Windows 上的 Linux 容器(LCOW)不受支持。

  • 对你来说可能最相关的是:为混合 Linux/Windows 集群设置本地 Kubernetes 开发环境非常复杂,目前没有标准解决方案,比如 Minikube 或 Windows 的 Docker Desktop,支持这样的配置。这意味着你需要一个本地的多节点集群或托管的云服务来开发和评估你的场景。

  • Windows 节点的加入过程不像 Linux 节点那样自动化。Kubeadm 很快将支持加入 Windows 节点的过程,但在那之前,你必须手动进行(借助一些 Powershell 脚本的帮助)。

对于容器工作负载/计算,一些限制如下:

  • Windows 节点不支持特权容器。这可能会带来其他一些限制,比如运行必须以特权模式运行的 CSI 插件。

  • Windows 没有内存进程杀手,目前 Pods 无法在内存使用方面受到限制。这对于进程隔离的容器是真实的,但一旦容器化 Hyper-V 隔离在 Kubernetes 上可用,就可以强制执行限制。

  • 你需要指定适当的节点选择器,以防止例如 Linux DaemonSets 尝试在 Windows 节点上运行。这在技术上不是一个限制,但你应该意识到你需要控制这些选择器来部署你的应用。

关于网络,一些限制如下:

  • Windows 节点的网络管理更加复杂,Windows 容器网络类似于 VM 网络。

  • Windows 上支持的网络插件(CNI)较少。你需要选择一个适用于集群中的 Linux 和 Windows 节点的解决方案,例如带有 host-gw 后端的 Flannel。

  • L2bridge、l2tunnel 或覆盖网络不支持 IPv6 栈。

  • Windows 的 Kube-proxy 不支持 IPVS 和高级负载均衡策略。

  • 从运行 Pod 的节点访问 NodePort 服务会失败。

  • Ingress Controllers 可以在 Windows 上运行,但只有在它们支持 Windows 容器的情况下;例如,ingress-nginx

  • 从集群内部使用 ICMP 数据包对外部网络主机进行 ping 不受支持。换句话说,当您使用 ping 测试从 Pod 到外部世界的连接时,不要感到惊讶。您可以使用curl或 Powershell Invoke-WebRequest代替。

对于存储,一些限制如下:

  • 无法扩展已挂载的卷。

  • 挂载到 Pod 的 Secrets 是使用节点存储以明文写入的。这可能存在安全风险,您需要采取额外的措施来保护集群。

  • Windows 节点仅支持以下卷类型:

  • FlexVolume(SMB,iSCSI)

  • azureDisk

  • azureFile

  • gcePersistentDisk

  • awsElasticBlockStore(自 1.16 版起)

  • vsphereVolume(自 1.16 版起)

以下限制涉及 Kubernetes 1.17 版。由于支持的功能和当前限制的列表会发生变化,我们建议您查看官方文档以获取更多最新详细信息:kubernetes.io/docs/setup/production-environment/windows/intro-windows-in-kubernetes/#supported-functionality-and-limitations

即使没有对 Windows 节点的本地开发集群的支持,我们仍然会对它们进行研究;很可能在不久的将来会支持 Windows 工作负载。

从头开始创建自己的开发集群

在本节中,您将学习如何在 Windows 操作系统上设置本地 Kubernetes 集群进行开发和学习。我们将使用 minikube,这是官方推荐的工具集,以及 Docker Desktop 用于 Windows Kubernetes 集群。请注意,当前的本地集群工具支持 Windows 容器,因为它需要使用 Linux 主节点和 Windows Server 节点进行多节点设置。换句话说,这些工具允许您在 Windows 计算机上开发运行在 Linux 容器中的 Kubernetes 应用程序。基本上,它们提供了一个优化的 Linux 虚拟机,用于托管一个节点的 Kubernetes 集群。

如果您希望进行实验,可以使用 Katacoda Kubernetes playground(www.katacoda.com/courses/kubernetes/playground),该平台用于演示本章中的 Kubernetes 对象,或者使用由 Docker, Inc.提供的 Play with Kubernetes(labs.play-with-k8s.com/)。

minikube

Minikube可用于 Windows、Linux 和 macOS,并旨在为 Kubernetes 的本地开发提供稳定的环境。在 Windows 上的关键要求是需要安装 VM 虚拟化程序。对于 Docker Desktop for Windows 和 Windows 容器,我们已经使用了 Hyper-V,因此这将是我们的选择。如果您尚未启用 Hyper-V,请按照第一章中安装 Docker Desktop for Windows 的说明,创建容器,或者按照官方文档:docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v

要安装 minikube,您需要执行以下步骤:

  1. 如果您没有 Hyper-V 虚拟外部网络交换机,请通过从开始菜单打开 Hyper-V 管理器并从操作选项卡中单击 Virtual Switch Manager...来创建一个。

  2. 选择 External 并单击 Create Virtual Switch。

  3. 使用 External Switch 作为虚拟交换机的名称,并选择要用于连接到互联网的网络适配器;例如,您的 Wi-Fi 适配器:

  1. 单击确定以接受更改。

  2. 使用Chocolatey软件包管理器安装 kubectl(Kubernetes CLI)。在 Powershell 窗口中以管理员身份执行以下命令:

choco install kubernetes-cli
  1. 使用 Chocolatey 安装 minikube,也作为管理员:
choco install minikube
  1. 将 Hyper-V 设置为 minikube 的默认虚拟化驱动程序:
minikube config set vm-driver hyperv
  1. 将您的虚拟外部交换机默认设置为 minikube:
minikube config set hyperv-virtual-switch "External Switch"
  1. 启动 minikube。这可能需要几分钟,因为需要设置 VM 并初始化 Kubernetes 节点:
minikube start

如果您需要在实际的 minikube VM 上调试问题(例如连接问题),您可以使用minikube ssh命令或直接从 Hyper-V 管理器连接到终端。登录用户名是docker,密码是tcuser

  1. 通过运行kubectl命令来验证安装是否成功,该命令将配置为连接到 minikube 集群。你应该看到kube-system命名空间中运行着各种 Pod:
kubectl get pods --all-namespaces
  1. 你可以使用本章中使用的任何示例 Kubernetes 对象,或者创建你自己的对象:
kubectl apply -f https://raw.githubusercontent.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/master/Chapter04/03_deployment-example/nginx-deployment.yaml --record
  1. 最终,你可以尝试在 web 浏览器中使用 Kubernetes 仪表板。要初始化并打开仪表板,运行以下命令:
minikube dashboard

现在,我们将看另一种使用 Windows Docker 桌面版进行本地开发的方法。

Windows 下的 Docker 桌面版

对于 Windows 用户来说,使用 Docker 桌面版和其内置的本地 Kubernetes 集群是最简单的方法。如果你在需要代理连接到互联网的环境中工作,建议使用这种方法,因为与 minikube 相比,设置是无缝的且更容易。

如果你还没有安装 Windows Docker 桌面版,你应该按照第一章 创建容器中的说明进行操作。要启用本地 Kubernetes 集群,你需要按照以下步骤进行:

  1. 确保你正在 Linux 容器模式下运行。DockerDesktopVM 将负责托管 Kubernetes 集群。为此,打开 Windows Docker 桌面版的托盘图标,然后点击切换到 Linux 容器....

  2. 操作完成后,从托盘图标中打开设置。

  3. 打开 Kubernetes 部分。

  4. 勾选启用 Kubernetes 复选框,然后点击应用。

  5. 设置过程将需要几分钟来完成。

  6. 如果你已经设置了 minikube,你需要切换上下文到 kubectl。从命令行中运行以下命令:

kubectl config use-context docker-desktop
  1. 或者,你也可以从 Windows 托盘中切换 Docker 桌面版的上下文:

你将在第六章 与 Kubernetes 集群交互中了解更多有关 kubectl 配置及其上下文的信息。

  1. 现在,你可以开始使用本地 Kubernetes 集群进行开发。让我们部署 Kubernetes 仪表板:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml
  1. 等待所有的 Pod 都处于运行状态:
 kubectl get pods --all-namespaces --watch
  1. 获取默认服务账户令牌。从命令输出中复制token:的值:
kubectl describe secret -n kube-system default
  1. 为集群启用 kubectl 代理。在访问仪表板时,这个过程应该在运行中:
kubectl proxy
  1. 导航到localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/#!/overview?namespace=kube-system

  2. 选择令牌,粘贴您的默认令牌,然后登录。

使用 Windows 容器支持设置本地开发 Kubernetes 集群的替代策略涉及在本地机器上使用自动设置的 VM,例如 vagrant。您可以探索 GitHub 上使用此方法的一些小项目,但您应该期望它们已过时且不再受支持。

在下一节中,我们将简要概述我们可以为 Kubernetes 集群执行的生产集群部署策略,特别是带有 Windows 节点的情况。

生产集群部署策略

生产级别集群的部署甚至带有 Windows 节点的集群的开发需要非常不同的方法。有三个重要问题决定了您部署 Kubernetes 集群的选项:

  • 您是在云中部署集群还是使用本地裸机或虚拟机?

  • 您需要高可用性HA)设置吗?

  • 您需要 Windows 容器支持吗?

让我们总结目前最流行的部署工具。

kubeadm

第一个是kubeadm (github.com/kubernetes/kubeadm),这是一个命令行工具,专注于以用户友好的方式启动并运行最小可行的安全集群。kubeadm 的一个方面是,它是一个仅限于给定机器和 Kubernetes API 通信的工具,因此,一般来说,它旨在成为管理整个集群的其他自动化工具的构建块。其原则很简单:在主节点上使用kubeadm init命令,在工作节点上使用kubeadm join。kubeadm 的特性可以总结如下:

  • 您可以在本地环境和云环境中部署集群。

  • 高可用集群得到支持,但截至 1.17 版本,此功能仍处于测试阶段。

  • 目前计划在版本 1.18 上提供官方的 Windows 支持。当前版本的 kubeadm 是启动混合 Kubernetes 集群的良好基础。首先,您可以引导主节点和(可选)Linux 工作节点,然后继续使用微软提供的用于加入 Windows 节点的脚本(github.com/microsoft/SDN)或在 sig-windows-tools GitHub 存储库中预览脚本的版本(github.com/kubernetes-sigs/sig-windows-tools)。我们将在第七章《部署混合本地 Kubernetes 集群》中使用这种方法。

如果您计划自动化 Kubernetes 集群的部署方式,例如使用 Ansible,kubeadm 是一个很好的起点,因为它提供了很大程度的灵活性和易配置性。

kops

下一个选项是使用Kubernetes Operationskopsgithub.com/kubernetes/kops),它在内部使用 kubeadm。Kops 旨在管理云环境中的整个 Kubernetes 集群-您可以将其视为用于集群的 kubectl。其主要特点如下:

  • 在 Amazon Web Services(官方支持)、Google Compute Engine 和 OpenStack(它们都处于测试阶段)上部署集群。除非您正在运行自己的 OpenStack 部署,否则不支持本地部署。VMware vSphere 支持处于 alpha 阶段。

  • 对 HA 集群的生产级支持。

  • 不支持 Windows 节点。

在本书中,我们不会关注 kops,因为它不支持 Windows。

kubespray

Kubespraygithub.com/kubernetes-sigs/kubespray)是一组可配置的 Ansible playbooks,运行 kubeadm 以引导完全功能的、可用于生产的 Kubernetes 集群。kubespray 和 kops 的主要区别在于,kops 与云提供商更紧密集成,而 kubespray 旨在支持多个平台,包括裸金属部署。其特点可以总结如下:

  • 支持为多个云提供商和裸金属机器安装 Kubernetes 集群。

  • 对 HA 集群的生产级支持。

  • 目前不支持 Windows 节点,但随着 kubeadm 对 Windows 节点的支持,kubespray 是最佳候选来扩展其支持。

由于 kubespray 目前不支持 Windows 节点,我们在本书中不会重点介绍它。

AKS 引擎

AKS 引擎github.com/Azure/aks-engine)是一个官方的开源工具,用于在 Azure 上提供自管理的 Kubernetes 集群。它旨在生成Azure 资源管理器ARM)模板,引导 Azure 虚拟机并设置集群。

不应将 AKS 引擎与Azure Kubernetes 服务AKS)混淆,后者是 Azure 提供的完全托管的 Kubernetes 集群服务。AKS 引擎在内部由 AKS 使用。

其特点可以总结如下:

但是,请注意,AKS 引擎提供了一些实验性功能,这些功能目前作为托管的 AKS 服务还不可用。这意味着,根据您使用的 AKS 引擎功能,这种方法可能并不总是适合运行生产工作负载。

托管的 Kubernetes 提供商

随着 Kubernetes 的不断普及,不同的云提供商和专门从事 Kubernetes 的公司提供了多个完全托管的 Kubernetes 服务。您可以在kubernetes.io/docs/setup/#production-environment找到一个长长的但不完整的 Kubernetes 提供商列表(不仅仅是托管)。在本节中,我们将总结一级云服务提供商的托管服务以及它们在 Windows 支持方面提供的服务,即以下内容:

  • 微软 Azure:Azure Kubernetes 服务AKS

  • 谷歌云平台:谷歌 Kubernetes 引擎GKE

  • 亚马逊网络服务:弹性 Kubernetes 服务EKS

对于托管的 Kubernetes 提供商,关键原则是您不负责管理控制平面、数据平面和基础集群基础设施。从您的角度来看,您会得到一个已准备好的集群(可能会根据需求进行扩展),具有高可用性和适当的 SLA。您只需要部署您的工作负载!另一种较少托管的方法是即插即用的云解决方案,在这种情况下,您自己管理控制平面、数据平面和升级,但基础设施由云提供商管理。这种解决方案的一个很好的例子是在 Azure VM 上运行的AKS Engine

所有这些云提供商在其托管的 Kubernetes 提供中都支持 Windows 容器,并且对于所有这些提供商,此功能目前处于预览阶段。您可以期待对该功能的有限支持和有限的向后兼容性。

2019 年 5 月,Azure Kubernetes 服务引入了对 Windows 节点的支持,并且是 Windows 容器的最成熟的提供者,其文档中有很好的支持(docs.microsoft.com/en-us/azure/aks/windows-container-cli)。这个提供是在 AKS Engine 内部构建的,因此您可以期待类似的功能也可以在那里使用。您可以通过访问github.com/Azure/AKS/projects/1来监视即将到来的 Windows 支持功能的官方路线图。

Google Kubernetes 引擎在 2019 年 5 月宣布在其 Rapid 发布通道中支持 Windows 容器。目前,关于这个 alpha 功能的信息有限-对于 Google 云平台来说,部署 Kubernetes 用于 Windows 直接到 Google Compute Engine VMs 是最常见和经过验证的用例。

2019 年 3 月,亚马逊弹性 Kubernetes 服务宣布支持 Windows 容器的预览。您可以在官方文档中找到有关 EKS 中 Windows 容器支持的更多详细信息:docs.aws.amazon.com/eks/latest/userguide/windows-support.html

创建带有 Windows 节点的 AKS 集群

要完成这个演练,您需要一个 Azure 账户和在您的机器上安装 Azure CLI。您可以在第二章中找到更多详细信息,即在容器中管理状态

以下步骤也可以在本书的官方 GitHub 存储库中作为 Powershell 脚本使用:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter04/05_CreateAKSWithWindowsNodes.ps1

让我们开始启用 AKS 的预览功能:

  1. 使用 Azure CLI 从 Powershell 安装aks-preview扩展:
az extension add --name aks-preview
  1. aks-preview扩展更新到最新可用版本:
az  extension  update --name aks-preview
  1. 为您的订阅注册WindowsPreview功能标志,以启用多个节点池。Windows 节点需要单独的节点池。请注意,此操作应在测试或开发订阅上执行,因为在启用此标志后创建的任何集群都将使用此功能:
az  feature  register `
   --name WindowsPreview `
   --namespace Microsoft.ContainerService
  1. 此操作将需要几分钟时间。您必须等到功能的“状态”为“已注册”才能继续。要检查当前的“状态”,运行以下命令:
az feature list `
 -o json `
 --query "[?contains(name, 'Microsoft.ContainerService/WindowsPreview')].{Name:name,State:properties.state}"
  1. 当功能注册后,执行以下命令来传播更改:
az  provider  register `
 --namespace Microsoft.ContainerService
  1. 现在,等待提供程序完成注册并将状态切换为“已注册”。您可以使用以下命令监视状态:
 az provider show -n Microsoft.ContainerService `
 | ConvertFrom-Json `
 | Select -ExpandProperty registrationState

AKS 的实际成本取决于托管集群的 Azure VM 的数量和大小。您可以在这里找到运行 AKS 集群的预测成本:azure.microsoft.com/en-in/pricing/details/kubernetes-service/。建议如果您在完成本教程后不打算使用集群,则删除集群以避免额外费用。

启用预览功能后,您可以继续创建具有 Windows 节点的实际 AKS 集群。Kubernetes 的可用版本取决于您创建集群的位置。在本教程中,我们建议使用westeurope Azure 位置。按照以下步骤创建集群:

  1. 为您的 AKS 集群创建一个专用资源组,例如aks-windows-resource-group
az  group  create `
   --name aks-windows-resource-group `
   --location westeurope
  1. 获取给定位置的可用 Kubernetes 版本列表:
 az aks get-versions `
 --location westeurope
  1. 选择所需的版本。建议使用最新版本;例如,1.15.3

  2. 使用所选版本创建一个aks-windows-cluster AKS 实例,并提供所需的 Windows 用户名和密码(选择一个安全的!)。以下命令将创建一个运行在 VMSS 高可用性模式下的 Linux 节点的两节点池:

az aks create `
 --resource-group aks-windows-resource-group `
 --name aks-windows-cluster `
 --node-count 2 `
 --enable-addons monitoring `
 --kubernetes-version 1.15.3 `
 --generate-ssh-keys `
 --windows-admin-username azureuser `
 --windows-admin-password "S3cur3P@ssw0rd" `
 --enable-vmss `
 --network-plugin azure
  1. 几分钟后,当 AKS 集群准备就绪时,将一个名为w1pool的 Windows 节点池添加到集群 - 此操作将需要几分钟。Windows 节点池名称的字符限制为六个:
az aks nodepool add `
 --resource-group aks-windows-resource-group `
 --cluster-name aks-windows-cluster `
 --os-type Windows `
 --name w1pool `
 --node-count 1 `
 --kubernetes-version 1.15.3
  1. 如果您尚未安装kubectl,请使用 Azure CLI 进行安装:
az aks install-cli
  1. 获取kubectl的集群凭据。以下命令将为kubectl添加一个新的上下文并切换到它:
az aks get-credentials `
   --resource-group aks-windows-resource-group `
   --name aks-windows-cluster
  1. 验证集群是否已成功部署!运行任何kubectl命令:
kubectl get nodes kubectl get pods --all-namespaces
  1. 现在,您可以开始使用具有 Windows 节点的第一个 Kubernetes 集群进行编程!例如,创建一个示例部署,其中在 Windows 容器中运行官方 ASP.NET 示例的三个副本,这些副本位于 LoadBalancer 类型的服务后面:
kubectl apply -f https://raw.githubusercontent.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/master/Chapter04/06_windows-example/windows-example.yaml --record
  1. 容器创建过程可能需要长达 10 分钟,因为需要首先拉取 Windows 基础映像。等待外部负载均衡器 IP 可用:
PS C:\> kubectl get service
NAME              TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)        AGE
kubernetes        ClusterIP      10.0.0.1      <none>          443/TCP        32m
windows-example   LoadBalancer   10.0.179.85   13.94.168.209   80:30433/TCP   12m
  1. 在 Web 浏览器中导航到地址以检查您的应用程序是否正常运行:

要删除 AKS 集群,请使用az group delete --name aks-windows-resource-group --yes --no-wait命令。

恭喜!您已成功创建了您的第一个完全托管的具有 Windows 节点的 Kubernetes 集群。在接下来的几章中,我们将探讨使用不同方法创建支持 Windows 容器的 Kubernetes 集群。

总结

在本章中,您了解了 Kubernetes 背后的关键理论 - 其高级架构和最常用的 Kubernetes API 对象。除此之外,我们总结了 Kubernetes 目前如何适应 Windows 生态系统以及 Windows 支持中的当前限制。接下来,您将学习如何使用推荐工具(如 minikube 和 Docker Desktop for Windows)为 Linux 容器设置自己的 Kubernetes 开发环境,以及可用的可能的生产集群部署策略。最后,我们回顾了支持 Windows 容器的托管 Kubernetes 产品,并成功部署了带有 Windows 节点池的 Azure Kubernetes Service 集群!

下一章将为您带来更多关于 Kubernetes 架构的知识-一般情况下以及在 Windows 生态系统中的 Kubernetes 网络。这将是最后一章,重点关注 Kubernetes 的理论和工作原理。

问题

  1. Kubernetes 中控制平面和数据平面之间有什么区别?

  2. 声明模型和期望状态的概念是如何工作的,它的好处是什么?

  3. 容器和 Pod 之间有什么区别?

  4. 部署 API 对象的目的是什么?

  5. Kubernetes 在 Windows 上的主要限制是什么?

  6. minikube 是什么,何时应该使用它?

  7. AKS 和 AKS Engine 之间有什么区别?

您可以在本书的评估部分找到这些问题的答案。

进一步阅读

第五章:Kubernetes 网络

对于容器编排,有两个主要挑战需要解决:管理容器主机(节点)和管理容器之间的网络。如果将容器主机集群限制为仅一个节点,网络将会相当简单——对于 Linux 上的 Docker,您将使用默认的桥接网络驱动程序,它创建一个私有网络(内部到主机),允许容器相互通信。对容器的外部访问需要暴露和映射容器端口作为主机端口。但是,现在如果考虑多节点集群,这个解决方案就不太适用——您必须使用 NAT 并跟踪使用了哪些主机端口,而且运行在容器中的应用程序还必须了解网络拓扑。

幸运的是,Kubernetes 通过提供一个具有特定基本要求的网络模型来解决这一挑战——符合规范的任何网络解决方案都可以作为 Kubernetes 中的网络模型实现。该模型的目标是提供透明的容器间通信和对容器的外部访问,而无需容器化应用程序了解底层网络挑战。在本章中,我们将解释 Kubernetes 网络模型的假设以及如何在混合 Linux/Windows 集群中解决 Kubernetes 网络问题。

在本章中,我们将涵盖以下主题:

  • Kubernetes 网络原则

  • Kubernetes CNI 网络插件

  • Kubernetes 中的 Windows 服务器网络

  • 选择 Kubernetes 网络模式

技术要求

对于本章,您将需要以下内容:

  • 安装了 Windows 10 Pro、企业版或教育版(1903 版本或更高版本,64 位)

  • 安装了 Docker Desktop for Windows 2.0.0.3 或更高版本

  • 如果您想要使用上一章中的 AKS 集群,则需要安装 Azure CLI

Docker Desktop for Windows 的安装和系统要求在第一章中有介绍,创建容器。

对于 Azure CLI,您可以在第二章中找到详细的安装说明,管理容器中的状态。

您可以从官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter05

Kubernetes 网络原则

作为容器编排器,Kubernetes 提供了一个网络模型,其中包含任何给定网络解决方案必须满足的一组要求。最重要的要求如下:

  • 在节点上运行的 Pod 必须能够与所有节点上的所有 Pod 进行通信(包括 Pod 所在的节点),而无需 NAT 和显式端口映射。

  • 在节点上运行的所有 Kubernetes 组件,例如 kubelet 或系统守护程序/服务,必须能够与该节点上的所有 Pod 进行通信。

这些要求强制执行了一个平面、无 NAT 的网络模型,这是使 Kubernetes 如此强大、可扩展和易于使用的核心概念之一。从这个角度来看,Pod 类似于在 Hyper-V 集群中运行的 VMs——每个 Pod 都分配了自己的 IP 地址(IP-per-Pod 模型),Pod 内的容器共享相同的网络命名空间(就像 VM 上的进程),这意味着它们共享相同的本地主机并且需要知道端口分配。

简而言之,Kubernetes 中的网络有以下挑战需要克服:

  • 容器内部的 Pod 间通信:由标准的本地主机通信处理。

  • Pod 间通信:由底层网络实现处理。

  • Pod 到 Service 和外部到 Service 的通信:由 Service API 对象处理,通信取决于底层网络实现。我们将在本节后面介绍这一点。

  • 当创建新的 Pod 时,kubelet 自动设置网络:由容器网络接口CNI)插件处理。我们将在下一节中介绍这一点。

Kubernetes 网络模型有许多实现,从简单的 L2 网络(例如,带有 host-gw 后端的 Flannel)到复杂的高性能软件定义网络SDN)解决方案(例如,Big Cloud Fabric)。您可以在官方文档中找到不同实现的网络模型的列表:kubernetes.io/docs/concepts/cluster-administration/networking/#how-to-implement-the-kubernetes-networking-model

本书中,我们将只关注从 Windows 角度相关的实现:

  • L2 网络

  • 覆盖网络

让我们从最简单的网络实现 L2 网络开始。

L2 网络

第二层L2)指的是数据链路层,是网络协议设计的七层 OSI 参考模型中的第二层。该层用于在同一局域网中的节点之间传输数据(因此,考虑在 MAC 地址和交换机端口上操作,而不是 IP 地址,IP 地址属于 L3)。对于 Kubernetes,具有在每个 Kubernetes 节点上设置路由表的 L2 网络是满足 Kubernetes 网络模型实现要求的最简单的网络类型。一个很好的例子是带有 host-gw 后端的 Flannel。在高层次上,Flannel(host-gw)以以下方式为 Pod 提供网络:

  1. 每个节点都运行一个flanneld(或者 Windows 上的flanneld.exe)代理,负责从一个称为Pod CIDR无类别域间路由)的较大的预配置地址空间中分配子网租约。在下图中,Pod CIDR 是10.244.0.0/16,而节点 1 租用了子网10.244.1.0/24,节点 2 租用了子网10.244.2.0/24

  2. 在大多数情况下,Flannel 代理在集群中进行 Pod 网络安装时部署为DaemonSet。可以在这里找到一个示例 DaemonSet 定义:github.com/coreos/flannel/blob/master/Documentation/kube-flannel.yml

  3. Flannel 使用 Kubernetes API 或etcd直接存储网络信息和租约数据,具体取决于其配置。

  4. 当新节点加入集群时,Flannel 为给定节点上的所有 Pod 创建一个cbr0桥接口。节点上的操作系统中的路由表会被更新,其中包含集群中每个节点的一个条目。例如,在下图中的 Node 2 中,路由表有两个条目,分别通过10.0.0.2网关(到 Node 1 的节点间通信)路由到10.244.1.0/24,以及通过本地cbr0接口(Node 1 上 Pod 之间的本地通信)路由到10.244.2.0/24

  5. 当创建一个新的 Pod 时,会创建一个新的veth设备对。在 Pod 网络命名空间中创建一个eth0设备,以及在主机(根)命名空间中对端的vethX设备。虚拟以太网设备用作网络命名空间之间的隧道。

  6. 为了触发上述操作,kubelet 使用了由 Flannel CNI 插件实现的 CNI:

Flannel 执行的所有操作都可以通过命令行手动执行,但是,Flannel 的目标当然是自动化新节点注册和新 Pod 网络创建的过程,对 Kubernetes 用户来说是透明的。

现在让我们快速分析一下当 Pod 1 中的容器10.244.1.2(位于 Node 1 上)想要向 Pod 4 中的容器10.244.2.3(位于 Node 2 上)发送 TCP 数据包时会发生什么:

  1. Pod 1 的出站数据包将被发送到cbr0桥接口,因为它被设置为eth0 Pod 接口的默认网关。

  2. 由于 Node 1 上的10.244.2.0/24 → 10.0.0.3路由表条目,数据包被转发到10.0.0.3网关。

  3. 数据包通过物理 L2 网络交换机,并在 Node 2 的eth0接口接收。

  4. Node 2 的路由表包含一个条目,将流量转发到本地的cbr0桥接口的10.244.2.0/24 CIDR。

  5. 数据包被 Pod 2 接收。

请注意,上述示例使用了 Linux 网络接口命名和术语。这个模型的 Windows 实现通常是相同的,但在操作系统级别的原语上有所不同。

使用带有路由表的 L2 网络是高效且简单的设置;然而,它也有一些缺点,特别是在集群规模扩大时:

  • 需要节点的 L2 邻接性。换句话说,所有节点必须在同一个本地区域网络中,中间没有 L3 路由器。

  • 在所有节点之间同步路由表。当新节点加入时,所有节点都需要更新它们的路由表。

  • 由于 L2 网络交换机在转发表中设置新的 MAC 地址的方式,可能会出现可能的故障和延迟,特别是对于短暂存在的容器。

带有 host-gw 后端的 Flannel 对 Windows 有稳定的支持。

一般来说,建议使用覆盖网络,这允许在现有的底层 L3 网络上创建一个虚拟的 L2 网络。

覆盖网络

作为一个一般概念,覆盖网络使用封装来创建一个新的、隧道化的虚拟网络,位于现有的 L2/L3 网络之上,称为底层网络。这个网络是在不对底层网络的实际物理网络基础设施进行任何更改的情况下创建的。覆盖网络中的网络服务通过封装与底层基础设施分离,封装是一种使用另一种类型的数据包来封装一种类型的数据包的过程。进入隧道时封装的数据包然后在隧道的另一端进行解封装。

覆盖网络是一个广泛的概念,有许多实现。在 Kubernetes 中,其中一个常用的实现是使用**虚拟可扩展局域网(VXLAN)**协议通过 UDP 数据包进行 L2 以太网帧的隧道传输。重要的是,这种类型的覆盖网络对 Linux 和 Windows 节点都适用。如果你有一个带有 VXLAN 后端的 Flannel 网络,Pods 的网络是以以下方式提供的:

  1. 类似于 host-gw 后端,每个节点上都部署了一个 flanneld 代理作为 DaemonSet。

  2. 当一个新的节点加入集群时,Flannel 为给定节点上的所有 Pods 创建一个cbr0桥接口和一个额外的flannel.<vni>VXLAN 设备(一个 VXLAN 隧道端点,或者简称为 VTEP;VNI 代表 VXLAN 网络标识符,在这个例子中是1)。这个设备负责流量的封装。IP 路由表只对新节点进行更新。发送到在同一节点上运行的 Pod 的流量被转发到cbr0接口,而所有剩余的发送到 Pod CIDR 的流量被转发到 VTEP 设备。例如,在下图中的节点 2,路由表有两个条目,将通信路由到10.244.0.0/16通过flannel.1 VTEP 设备(覆盖网络中的节点间通信),并且将通信路由到10.244.2.0/24通过本地的cbr0接口(节点 1 上的 Pod 之间的本地通信)。

  3. 当创建一个新的 Pod 时,会创建一个新的 veth 设备对,类似于 host-gw 后端的情况:

现在让我们快速分析当 Pod 1 中的容器10.244.1.2(在节点 1 上)想要向 Pod 4 中的容器10.244.2.3(在节点 2 上)发送 TCP 数据包时会发生什么:

  1. Pod 1 的出站数据包将被发送到cbr0桥接口,因为它被设置为eth0 Pod 接口的默认网关。

  2. 由于节点 1 上的10.244.0.0/16 → flannel.1路由表条目,数据包被转发到flannel.1 VTEP 设备。

  3. flannel.1使用10.244.0.0/16叠加网络中 Pod 4 的 MAC 地址作为内部数据包的目的地址。这个地址是由flanneld代理在转发数据库FDB)中填充的。

  4. flannel.1使用 FDB 确定节点 2 的目标 VTEP 设备的 IP 地址,并使用10.0.0.3作为外部封装数据包的目的地址。

  5. 数据包通过物理 L2/L3 网络传输,并被节点 2 接收。数据包由flannel.1 VTEP 设备进行解封装。

  6. 节点 2 的路由表包含一个条目,将流量转发到本地的cbr0桥接口的10.244.2.0/24 CIDR。

  7. Pod 2 接收到数据包。

对于 Windows,Flannel 与 Overlay 后端目前仍处于 alpha 功能阶段。

使用 VXLAN 后端而不是 host-gw 后端的 Flannel 具有几个优势:

  • 节点之间不需要 L2 邻接。

  • L2 叠加网络不容易受到生成树故障的影响,这种情况可能发生在跨多个逻辑交换机的 L2 域的情况下。

本节中描述的解决方案类似于 Docker 在swarm 模式下运行。您可以在官方文档中了解有关 swarm 模式的 Overlay 网络的更多信息:docs.docker.com/network/overlay/.

前两种网络解决方案是混合 Linux/Windows 集群中最常用的解决方案,特别是在本地运行时。对于其他情况,也可以使用Open Virtual NetworkOVN)和L2 隧道进行 Azure 特定实现。

其他解决方案

就 Kubernetes 支持的 Windows 网络解决方案而言,还有两种额外的实现可以使用:

  • 例如,Open Virtual NetworkOVN)作为 OpenStack 部署的一部分

  • 在 Azure 部署中使用L2 隧道

OVN 是一个用于实现 SDN 的网络虚拟化平台,它将物理网络拓扑与逻辑网络拓扑解耦。使用 OVN,用户可以定义由逻辑交换机和路由器组成的网络拓扑。Kubernetes 支持使用专用 CNI 插件ovn-kubernetesgithub.com/ovn-org/ovn-kubernetes)进行 OVN 集成。

对于特定于 Azure 的场景,可以使用Azure-CNI插件,该插件依赖于L2Tunnel Docker 网络驱动程序,直接利用 Microsoft Cloud Stack 功能。简而言之,Pod 连接到现有的虚拟网络资源和配置,并且所有 Pod 数据包直接路由到虚拟化主机,以应用 Azure SDN 策略。Pod 在 Azure 提供的虚拟网络中获得完全的连通性,这意味着每个 Pod 都可以直接从集群外部访问。您可以在官方 AKS 文档中找到有关此解决方案的更多详细信息:docs.microsoft.com/bs-latn-ba/azure/aks/configure-azure-cni

服务

在上一章中,我们介绍了服务作为 API 对象,并解释了它们如何与部署一起使用。简单回顾一下,服务 API 对象基于标签选择器,使一组 Pod 可以进行网络访问。在 Kubernetes 网络方面,服务是建立在标准网络模型之上的概念,旨在实现以下目标:

  • 使用虚拟 IPVIP)可靠地与一组 Pod 进行通信。客户端 Pod 不需要知道单个 Pod 的当前 IP 地址,因为这些地址随时间可能会发生变化。外部客户端也不需要知道 Pod 的当前 IP 地址。

  • 将网络流量(内部和外部)负载均衡到一组 Pod。

  • 在集群中启用服务发现。这需要在集群中运行 DNS 服务附加组件。

Kubernetes 中有四种可用的服务类型,可以在服务对象规范中指定。

  • ClusterIP

  • NodePort

  • LoadBalancer

  • ExternalName

我们将分别讨论每种类型,但首先让我们看看在部署和 Pod 的上下文中服务是什么样子的:

上述图表显示了 ClusterIP 类型的最简单的内部 Service 是如何公开管理三个带有environment: test标签的 Pod 的现有部署。具有相同标签选择器environment: test的 ClusterIP Service 负责监视标签选择器评估的结果,并使用当前的存活和准备好的 Pod IP 更新endpoint API 对象。同时,kube-proxy 观察 Service 和 endpoint 对象,以在 Linux 节点上创建 iptables 规则或在 Windows 节点上创建 HNS 策略,用于实现具有 Service 规范中指定的 ClusterIP 值的虚拟 IP 地址。最后,当客户端 Pod 向虚拟 IP 发送请求时,它将使用 kube-proxy 设置的规则/策略转发到部署中的一个 Pod。正如您所看到的,kube-proxy 是实现服务的中心组件,实际上它用于所有服务类型,除了 ExternalName。

ClusterIP

Kubernetes 中默认的 Service 类型是 ClusterIP,它使用内部 VIP 公开服务。这意味着 Service 只能在集群内部访问。假设您正在运行以下nginx部署:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment-example
spec:
  replicas: 3
  selector:
    matchLabels:
      environment: test
  template:
    metadata:
      labels:
        environment: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.17
        ports:
        - containerPort: 80

所有清单文件都可以在本书的官方 GitHub 存储库中找到:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter05

您可以使用以下清单文件部署 ClusterIP 类型的 Service:

apiVersion: v1
kind: Service
metadata:
  name: nginx-deployment-example-clusterip
spec:
  selector:
    environment: test
  type: ClusterIP
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 80

与每种 Service 类型一样,关键部分是selector规范,它必须与部署中的 Pod 匹配。您将type指定为ClusterIP,并在 Service 上分配8080作为端口,该端口映射到 Pod 上的targetPort: 80。这意味着客户端 Pod 将使用nginx-deployment-example:8080 TCP 端点与 nginx Pods 进行通信。实际的 ClusterIP 地址是动态分配的,除非您在spec中明确指定一个。Kubernetes 集群中的内部 DNS 服务负责将nginx-deployment-example解析为实际的 ClusterIP 地址,作为服务发现的一部分。

本节其余部分的图表表示了服务在逻辑上是如何实现的。在幕后,kube-proxy 负责管理所有转发规则和公开端口,就像前面的图表中一样。

这在以下图表中进行了可视化:

ClusterIP 服务是允许外部通信的其他服务类型的基础:NodePort 和 LoadBalancer。

NodePort

允许对 Pod 进行外部入口通信的第一种服务类型是 NodePort 服务。这种类型的服务被实现为 ClusterIP 服务,并具有使用任何集群节点 IP 地址和指定端口可达的额外功能。为了实现这一点,kube-proxy 在 30000-32767 范围内的每个节点上公开相同的端口(可配置),并设置转发,以便将对该端口的任何连接转发到 ClusterIP。

您可以使用以下清单文件部署 NodePort 服务:

apiVersion: v1
kind: Service
metadata:
  name: nginx-deployment-example-nodeport
spec:
  selector:
    environment: test
  type: NodePort
  ports:
  - port: 8080
    nodePort: 31001
    protocol: TCP
    targetPort: 80

如果在规范中未指定nodePort,则将动态分配使用 NodePort 范围。请注意,服务仍然充当 ClusterIP 服务,这意味着它在其 ClusterIP 端点内部可达。

以下图表可视化了 NodePort 服务的概念:

当您希望在服务前设置自己的负载均衡设置时,建议使用 NodePort 服务。您也可以直接暴露 NodePorts,但请记住,这样的解决方案更难以保护,并可能存在安全风险。

LoadBalancer

允许外部入口通信的第二种服务类型是 LoadBalancer,在可以创建外部负载均衡器的 Kubernetes 集群中可用,例如云中的托管 Kubernetes 服务。这种类型的服务将 NodePort 的方法与额外的外部负载均衡器结合在一起,该负载均衡器将流量路由到 NodePorts。

您可以使用以下清单文件部署 LoadBalancer 服务:

apiVersion: v1
kind: Service
metadata:
  name: nginx-deployment-example-lb
spec:
  selector:
    environment: test
  type: LoadBalancer
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 80

请注意,为了应用此清单文件,您需要支持外部负载均衡器的环境,例如我们在第四章中创建的 AKS 集群,Kubernetes 概念和 Windows 支持。Katacoda Kubernetes Playground 还能够创建可以从 Playground 终端访问的“外部”负载均衡器。如果您尝试在不支持创建外部负载均衡器的环境中创建 LoadBalancer 服务,将导致负载均衡器入口 IP 地址无限期处于pending状态。

为了获得外部负载均衡器地址,请执行以下命令:

PS C:\src> kubectl get svc nginx-deployment-example-lb
NAME                          TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)          AGE
nginx-deployment-example-lb   LoadBalancer   10.0.190.215   137.117.227.83   8080:30141/TCP   2m23s

EXTERNAL-IP列显示负载均衡器具有 IP 地址137.117.227.83,为了访问您的服务,您必须与137.117.227.83:8080 TCP 端点通信。此外,您可以看到服务有自己的内部 ClusterIP,10.0.190.215,并且公开了 NodePort 30141。在 AKS 上运行的 LoadBalancer 服务已在以下图表中可视化:

如果您对服务前面的 Azure 负载均衡器的配置感兴趣,您需要转到portal.azure.com并导航到负载均衡器资源,您将在那里找到 Kubernetes 负载均衡器实例:

现在,让我们来看看最后一种服务类型:ExternalName。

ExternalName

在某些情况下,您需要定义一个指向不托管在 Kubernetes 集群中的外部资源的服务。例如,这可能包括云托管的数据库实例。Kubernetes 提供了一种将通信抽象化到这些资源并通过使用 ExternalName 服务在集群服务发现中注册它们的方法。

ExternalName 服务不使用选择器,只是服务名称到外部 DNS 名称的原始映射:

apiVersion: v1
kind: Service
metadata:
  name: externalname-example-service
spec:
  type: ExternalName
  externalName: cloud.database.example.com

在解析服务 DNS 名称(externalname-example-service.default.svc.cluster.local)期间,内部集群 DNS 将响应具有值cloud.database.example.com的 CNAME 记录。没有使用 kube-proxy 规则进行实际的流量转发-重定向发生在 DNS 级别。

ExternalName 服务的一个很好的用例是根据环境类型提供外部服务的不同实例,例如数据库。从 Pod 的角度来看,这不需要任何配置或连接字符串更改。

Ingress

LoadBalancer 服务仅提供 L4 负载平衡功能。这意味着您不能使用以下内容:

  • HTTPS 流量终止和卸载

  • 使用相同的负载均衡器进行基于名称的虚拟主机托管多个域名

  • 基于路径的路由到服务,例如作为 API 网关

为了解决这个问题,Kubernetes 提供了 Ingress API 对象(不是服务类型),可用于 L7 负载平衡。

Ingress 部署和配置是一个广泛的主题,超出了本书的范围。您可以在官方文档中找到关于 Ingress 和 Ingress 控制器的更详细信息:kubernetes.io/docs/concepts/services-networking/ingress/

使用 Ingress 首先需要在您的 Kubernetes 集群中部署一个 Ingress 控制器。Ingress 控制器是一个 Kubernetes 控制器,通常作为一个 DaemonSet 或运行专用 Pod 来处理入口流量负载均衡和智能路由,手动部署到集群中。Kubernetes 中常用的 Ingress 控制器是ingress-nginxwww.nginx.com/products/nginx/kubernetes-ingress-controller),它作为一个 nginx web 主机的部署,具有一组规则来处理 Ingress API 对象。Ingress 控制器以一种取决于安装的类型的 Service 暴露出来。例如,对于只有 Linux 节点的 AKS 集群,可以使用以下清单执行 ingress-nginx 的基本安装,将其暴露为 LoadBalancer Service。

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/cloud-generic.yaml

一般来说,Ingress 控制器的安装取决于 Kubernetes 集群环境和配置,并且必须根据您的需求进行调整。例如,对于带有 Windows 节点的 AKS,您需要确保适当的节点选择器被使用,以便正确调度 Ingress 控制器 Pod。

您可以在本书的官方 GitHub 存储库中找到针对带有 Windows 节点的 AKS 的定制 nginx Ingress 控制器定义,以及示例服务和 Ingress 定义:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter05/05_ingress-example

当 Ingress 控制器已经安装在集群中时,Ingress API 对象可以被创建并且会被控制器处理。例如,假设您已经部署了两个 ClusterIP 服务example-service1example-service2,Ingress 定义可能如下所示:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /service1
        backend:
          serviceName: example-service1
          servicePort: 80
      - path: /service2
        backend:
          serviceName: example-service2
          servicePort: 80

现在,当您对https://<ingressServiceLoadBalancerIp>/service1发出 HTTP 请求时,流量将由 nginx 路由到example-service1。请注意,您只使用一个云负载均衡器进行此操作,实际的路由到 Kubernetes 服务是由 Ingress 控制器使用基于路径的路由来执行的。

这种设计原则已在以下图表中显示:

对于 AKS,您可以考虑使用 HTTP 应用程序路由附加组件,它可以自动管理 Ingress 控制器和集群的 External-DNS 控制器。更多细节可以在官方文档中找到:docs.microsoft.com/en-us/azure/aks/http-application-routing

选择是否实现 Ingress 或 Service 的一个一般准则是使用 Ingress 来暴露 HTTP(尤其是 HTTPS)端点,并使用 Service 来处理其他协议。

Kubernetes CNI 网络插件

在这一章中,我们已经提到了容器网络接口CNI)和CNI 插件这两个术语,这是在 Kubernetes 网络设置的背景下。事实上,CNI 并不局限于 Kubernetes——这个概念起源于 Rkt 容器运行时,并被采纳为 CNCF 项目,旨在为任何容器运行时和网络实现提供一个简单明了的接口。容器运行时使用 CNI 插件来连接容器到网络,并在需要时将它们从网络中移除。

理解 CNI 项目

CNI 项目有三个明确的部分:

CNI 的规范非常简单明了,可以总结如下:

  • CNI 插件是作为独立的可执行文件实现的。

  • 容器运行时负责在与 CNI 插件交互之前为容器准备一个新的网络命名空间(或在 Windows 情况下为网络隔间)。

  • CNI 插件负责将容器连接到由网络配置指定的网络。

  • 网络配置以 JSON 格式由容器运行时通过标准输入提供给 CNI 插件。

  • 使用环境变量向 CNI 插件提供参数。例如,CNI_COMMAND变量指定插件应执行的操作类型。命令集是有限的,包括ADDDELCHECKVERSION;其中最重要的是ADDDEL,分别用于将容器添加到网络和从网络中删除容器。

对于 CNI 插件,有三种常见类型的插件,在网络配置期间负责不同的责任:

  • 接口创建插件

  • **IP 地址管理(IPAM)**插件负责为容器分配 IP 地址。

  • 元插件可以作为其他 CNI 插件的适配器,或为其他 CNI 插件提供额外的配置或转换它们的输出。

目前,在 Windows 上只能使用以下参考实现:host-local IPAM 插件、win-bridge 和 win-Overlay 接口创建插件,以及 flannel 元插件。也可以使用第三方插件;例如,微软提供了 Azure-CNI 插件,用于将容器与 Azure SDN 集成(github.com/Azure/azure-container-networking/blob/master/docs/cni.md)。

在 Kubernetes 中,kubelet 在管理 Pod 的生命周期时使用 CNI 插件,以确保 Pod 的连通性和可达性。Kubelet 执行的最基本操作是在创建 Pod 时执行ADD CNI 命令,在销毁 Pod 时执行DELETE CNI 命令。在某些情况下,CNI 插件也可以用于调整 kube-proxy 的配置。

在部署新集群时,选择 CNI 插件并定义 CNI 插件的网络配置是在 Pod 网络附加组件安装步骤中执行的。最常见的安装方式是通过部署专用的 DaemonSet 来执行,该 DaemonSet 使用 init 容器执行 CNI 插件的安装,并在每个节点上运行额外的代理容器(如果需要的话)。这种安装的一个很好的例子是 Flannel 的官方 Kubernetes 清单:github.com/coreos/flannel/blob/master/Documentation/kube-flannel.yml

CoreOS Flannel

在使用 Linux/Windows 混合 Kubernetes 集群时,特别是在本地部署时,通常会将Flannel作为 Pod 网络附加组件安装(github.com/coreos/flannel)。Flannel 是一个面向多个节点的 Kubernetes 和容器的最小化 L2/L3 虚拟网络提供程序。Flannel 有三个主要组件:

  • 在集群中的每个节点上都运行一个flanneld(或者在 Windows 机器上是flanneld.exe)代理/守护进程,通常作为 Kubernetes 中的一个 DaemonSet 部署。它负责为每个节点分配一个较大的 Pod CIDR 中的独占子网租约。例如,在本章中,我们一直在集群中使用10.244.0.0/16作为 Pod CIDR,而在单个节点上使用10.244.1.0/2410.244.2.0/24作为子网租约。租约信息和节点网络配置由flanneld使用 Kubernetes API 或直接存储在etcd中。该代理的主要责任是同步子网租约信息,配置 Flannel 后端,并在节点上为其他组件(如 Flannel CNI 插件)公开配置(作为容器主机文件系统中的文件)。

  • Flannel 的后端定义了 Pod 之间的网络是如何创建的。在本章中我们已经使用过的在 Windows 和 Linux 上都支持的后端的例子有 Vxlan 和 host-gw。您可以在github.com/coreos/flannel/blob/master/Documentation/backends.md找到更多关于 Flannel 后端的信息。

  • Flannel CNI 插件是由 kubelet 在将 Pod 添加到网络或从网络中移除 Pod 时执行的。Flannel CNI 插件是一个元插件,它使用其他创建接口和 IPAM 插件来执行操作。它的责任是读取flanneld提供的子网信息,为适当的 CNI 插件生成 JSON 配置,并执行它。目标插件的选择取决于 Flannel 使用的后端;例如,在 Windows 节点上使用 vxlan 后端,Flannel CNI 插件将调用 host-local IPAM 插件和 win-Overlay 插件。您可以在官方文档中找到有关这个元插件的更多信息:github.com/containernetworking/plugins/tree/master/plugins/meta/flannel

让我们逐步看看在运行在 vxlan 后端的 Windows 节点上发生的事情——从 Flannel 代理部署到 kubelet 创建 Pod(类似的步骤也发生在 Linux 节点上,但执行不同的目标 CNI 插件):

  1. flanneld.exe代理作为一个 DaemonSet 部署到节点上,或者按照当前 Windows 文档的建议手动启动。

  2. 代理读取提供的net-conf.json文件,其中包含 Pod CIDR 和vxlan后端配置:

{
    "Network": "10.244.0.0/16",
    "Backend": {
        "Type": "vxlan",
        "VNI": 4096,
        "Port": 4789
    }
}
  1. 代理为节点获取一个新的子网租约10.244.1.0/24。租约信息存储在 Kubernetes API 中。创建vxlan0网络,创建 VTEP 设备,并更新路由表和转发数据库。

  2. 子网租约的信息被写入到节点文件系统中的C:\run\flannel\subnet.env。这是一个例子:

FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.1.0/24
FLANNEL_MTU=1472
FLANNEL_IPMASQ=true
  1. 每当一个新节点加入集群时,flanneld.exe代理会对路由表和转发数据库进行任何额外的重新配置。

  2. 现在,一个新的 Pod 被调度到这个节点上,kubelet 初始化 pod 基础容器,并在 Flannel meta CNI 插件上执行ADD命令,使用配置 JSON,该 JSON 将接口创建委托给win-Overlay插件,并将 IPAM 管理委托给host-local插件。Flannel CNI 插件根据subnet.env和这些插件的输入配置生成配置 JSON。

  3. 使用host-local IPAM 插件租用新的 IP。Flannel 不负责管理 IPAM,它只是从当前节点上的给定子网中检索一个新的空闲 IP 地址。

  4. win-bridge插件配置了 Pod 的主机网络服务(HNS)端点,并有效地将 Pod 连接到 Overlay 网络。

总之,Flannel 自动化了为 Pod 创建 L2/Overlay 网络的过程,并在创建新 Pod 或新节点加入集群时维护网络。目前,在 Windows 上,L2 网络(host-gw 后端)被认为是稳定的,而 Overlay 网络(vxlan 后端)在 Windows 上仍处于 alpha 阶段——在使用本地 Kubernetes 集群时,这两种后端都很有用。对于 AKS 和 AKS-engine 场景,安装 Pod 网络的最有效方式是使用默认的 Azure-CNI 插件。

Kubernetes 中的 Windows Server 网络

在高层次上,Windows 节点的 Kubernetes 网络与 Linux 节点类似——kubelet 通过 CNI 与网络操作解耦。主要区别在于 Windows 容器网络的实际实现以及用于 Windows 容器的术语。

Windows 容器网络设置类似于 Hyper-V 虚拟机网络,并且实际上共享许多内部服务,特别是主机网络服务HNS),它与主机计算服务HCS)合作,后者管理容器的生命周期。创建新的 Docker 容器时,容器会接收自己的网络命名空间(隔间)和位于该命名空间中的虚拟网络接口控制器vNIC或在 Hyper-V 中,隔离容器或vmNIC)。然后将 vNIC 连接到Hyper-V 虚拟交换机vSwitch),该交换机还使用主机 vNIC 连接到主机默认网络命名空间。您可以将此结构宽松地映射到 Linux 容器世界中的容器桥接口(CBR)。vSwitch 利用 Windows 防火墙和虚拟过滤平台VFP)Hyper-V vSwitch 扩展来提供网络安全、流量转发、VXLAN 封装和负载平衡。这个组件对于 kube-proxy 提供服务功能至关重要,您可以将 VFP 视为 Linux 容器世界中的iptables。vSwitch 可以是内部的(不连接到容器主机上的网络适配器)或外部的(连接到容器主机上的网络适配器);这取决于容器网络驱动程序。在 Kubernetes 的情况下,您将始终使用创建外部 vSwitch 的网络驱动程序(L2Bridge、Overlay、Transparent)。

VFP 利用 Windows 内核功能来过滤和转发网络流量。直到 Kubernetes 1.8,kube-proxy 不支持 VFP,唯一的转发流量的方式是使用用户空间模式,该模式在用户空间而不是内核空间中进行所有流量管理。

在创建容器时,所有前述设置都是由 HNS 执行的。HNS 通常负责以下工作:

  • 创建虚拟网络和 vSwitches

  • 创建网络命名空间(隔间)

  • 创建 vNICs(端点)并将它们放置在容器网络命名空间中

  • 创建 vSwitch 端口

  • 管理 VFP 网络策略(负载平衡、封装)

在 Kubernetes 的情况下,CNI 插件是设置容器网络的唯一方式(对于 Linux,可以选择不使用它们)。它们与 HNS 和 HCS 进行实际通信,以设置所选的网络模式。与标准的 Docker 网络设置相比,Kubernetes 的网络设置有一个重要的区别:容器 vNIC 连接到 pod 基础设施容器,并且网络命名空间在 Pod 中的所有容器之间共享。这与 Linux Pods 的概念相同。

这些结构在以下图表中可视化:

Windows 容器网络架构为 Kubernetes 有一个更重要的概念:网络驱动程序(模式)。在下一节中,我们将介绍选项,并看看它们中哪些适用于 Kubernetes,但首先,让我们快速看一下 Windows 上 Kubernetes 网络的当前限制。

限制

Windows 容器网络不断发展,许多功能的实现仍在进行中。目前,Kubernetes 在 Windows 平台上有一些网络限制。

  • Windows Pod 不支持主机网络模式。

  • 不支持从节点本身访问 NodePort。

  • L2Bridge、L2Tunnel 和 Overlay 网络驱动不支持 IPv6 堆栈。

  • 不支持外部网络的 ICMP。换句话说,您将无法 ping 通 Kubernetes 集群外的 IP 地址。

  • 在 vxlan 后端上运行的 Flannel 受限于使用 VNI 4096 和 UDP 端口 4789。

  • 不支持容器通信的 IPsec 加密。

  • 容器内不支持 HTTP 代理。

  • 对于在 Windows 节点上运行的 Ingress 控制器,您必须选择支持 Windows 和 Linux 节点的部署。

您可以期待这个列表变得更短,因为新版本的 Windows Server 和 Kubernetes 即将推出。

选择 Kubernetes 网络模式

网络模式(驱动程序)是 Docker 的一个概念,是容器网络模型CNM)的一部分。这个规范是由 Docker 提出的,以模块化、可插拔的方式解决容器网络设置和管理的挑战。Docker 的 libnetwork 是 CNM 规范的规范实现。

此时,您可能想知道 CNM 与 CNI 的关系,它们解决了类似的问题。是的,它们是竞争的容器网络规范!对于 Linux 容器,Docker 网络驱动程序和 CNI 的实现可能会有很大的不同。然而,对于 Windows 容器,libnetwork 中实现的网络驱动程序只是 HNS 的一个简单的包装,执行所有的配置任务。CNI 插件,如 win-bridge 和 win-Overlay,也是一样的:调用 HNS API。这意味着对于 Windows 来说,Docker 网络驱动程序和 CNI 插件是平等的,并且完全依赖于 HNS 及其本地网络配置。如果您感兴趣,您可以查看 libnetwork 的 Windows 驱动程序实现,并了解它是如何与 HNS 交互的:github.com/docker/libnetwork/blob/master/drivers/windows/windows.go

CNI 和 CNM 有着悠久的历史和一些显著的区别。在 Kubernetes 早期,决定不使用 Docker 的 libnetwork,而是选择 CNI 作为容器网络管理的抽象。您可以在 Kubernetes 的博客文章中阅读更多关于这个决定的信息:kubernetes.io/blog/2016/01/why-kubernetes-doesnt-use-libnetwork/。如果您对 CNI 与 CNM 的更多细节感兴趣,请参考这篇文章:thenewstack.io/container-networking-landscape-cni-coreos-cnm-docker/

总的来说,对于 Windows 容器,您可以互换使用 Docker 网络驱动程序和 HNS 网络驱动程序这两个术语。

目前 Windows 容器支持五种 HNS 网络驱动程序:

  • l2bridge

  • l2tunnel

  • Overlay

  • 透明

  • NAT(在 Kubernetes 中未使用)

您可以使用以下命令手动创建一个新的 Docker 网络:

docker network create -d <networkType> <additionalParameters> <name> 

某些网络类型需要额外的参数;您可以在官方文档中找到更多详细信息:docs.microsoft.com/en-us/virtualization/windowscontainers/container-networking/network-drivers-topologies。Microsoft SDN 仓库还提供了一个简单的 PowerShell 模块,用于与 HNS API 进行交互,您可以使用它来分析您的网络配置:github.com/microsoft/SDN/blob/master/Kubernetes/windows/hns.psm1

您可以在 Microsoft 的支持政策中找到 Windows 容器的官方支持的网络配置:support.microsoft.com/da-dk/help/4489234/support-policy-for-windows-containers-and-docker-on-premises

现在让我们逐个了解每种类型的 HNS 网络,以了解它们如何适用于 Kubernetes,何时使用它们,以及它们与 CNI 插件的关系。

L2Bridge

在 L2Bridge 网络模式下,容器连接到共享的外部 Hyper-V vSwitch,可以访问底层网络。容器还与容器主机共享相同的 IP 子网,容器 IP 地址必须使用与容器主机 IP 相同前缀的静态分配。MAC 地址在进入和离开时被重写为主机的地址(这需要启用 MAC 欺骗;在本地 Hyper-V VM 上测试 Kubernetes 集群时请记住这一点)。

以下 CNI 插件使用 L2Bridge 网络:

  • win-bridge

  • Azure-CNI

  • Flannel 使用 host-gw 后端(作为元插件,它调用 win-bridge)

以下是 L2Bridge 的优点:

  • win-bridge 和 Flannel(host-gw)易于配置

  • 在 Windows 中有稳定的支持

  • 最佳性能

以下是 L2Bridge 的缺点:

  • 节点之间需要 L2 邻接

L2Tunnel

L2Tunnel 网络模式是 L2Bridge 的特例,在此模式下,所有 来自容器的网络流量都被转发到虚拟化主机,以应用 SDN 策略。此网络类型仅适用于 Microsoft Cloud Stack。

以下 CNI 插件使用 L2Tunnel 网络:

  • Azure-CNI

以下是 L2Tunnel 的优点:

L2Tunnel 的缺点包括:

  • 它只能在 Azure 上使用

覆盖

覆盖网络模式使用 VFP 在外部 Hyper-V vSwitch 上创建 VXLAN 覆盖网络。每个覆盖网络都有自己的 IP 子网,由可定制的 IP 前缀确定。

以下 CNI 插件使用覆盖网络:

  • win-Overlay

  • 带有 vxlan 后端的 Flannel(作为元插件,调用 win-Overlay)

覆盖网络的优点包括:

  • 子网组织没有限制。

  • 节点之间不需要 L2 邻接。您可以在 L3 网络中使用此模式。

  • 增强了与底层网络的安全性和隔离性。

覆盖的缺点包括:

  • 它目前处于 Windows 的 alpha 功能阶段。

  • 您受限于特定的 VNI(4096)和 UDP 端口(4789)。

  • 性能比 L2Bridge 差。

Transparent

在 Windows 上,Kubernetes 支持的最后一个 HNS 网络类型是 Transparent。连接到透明网络的容器将连接到具有静态或动态分配的 IP 地址的外部 Hyper-V vSwitch。在 Kubernetes 中,此网络类型用于支持 OVN,其中逻辑交换机和路由器启用了 Pod 内部的通信。

以下 CNI 插件使用透明网络:

  • ovn-kubernetes

透明网络的缺点包括:

  • 如果您想在本地托管的 Kubernetes 中使用这种网络类型,您必须部署 OVN 和 Open vSwitches,这本身就是一个复杂的任务。

总结

在本章中,您已经了解了 Kubernetes 中网络的原则。我们介绍了 Kubernetes 网络模型和任何模型实现必须满足的要求。接下来,我们从 Windows 的角度分析了两种最重要的网络模型实现:L2 网络和覆盖网络。在上一章中,您已经了解了 Service API 对象,而在本章中,您更深入地了解了服务在网络模型方面的实现。最后,您了解了 Windows 节点上的 Kubernetes 网络、CNI 插件以及何时使用每种插件类型。

下一章将重点介绍如何使用 Kubernetes 命令行工具(即kubectl)从 Windows 机器与 Kubernetes 集群进行交互。

问题

  1. 实施 Kubernetes 网络模型的要求是什么?

  2. 在 Kubernetes 中何时可以使用带有 host-gw 后端的 Flannel?

  3. ClusterIP 和 NodePort 服务之间有什么区别?

  4. 使用 Ingress 控制器而不是 LoadBalancer 服务的好处是什么?

  5. CNI 插件是什么,它们如何被 Kubernetes 使用?

  6. 内部和外部 Hyper-V vSwitch 之间有什么区别?

  7. CNI 插件和 Docker 网络驱动之间有什么区别?

  8. 什么是覆盖网络?

您可以在本书的评估部分找到这些问题的答案。

进一步阅读

有关 Kubernetes 概念和网络的更多信息,请参考以下 Packt 图书和资源:

第六章:与 Kubernetes 集群交互

作为 Kubernetes 集群的用户或操作员,您需要与 Kubernetes API 交互,以管理 Kubernetes 对象或调试在集群中运行的应用程序。一般来说,有两种方式与 Kubernetes API 通信:您可以直接使用表征状态传输(RESTful)HTTPS 端点,例如用于编程访问,或者您可以使用 kubectl,这是 Kubernetes 命令行工具(或命令行接口CLI))。一般来说,kubectl 封装了 RESTful API 通信,并隐藏了有关定位和认证到 Kubernetes API 服务器的复杂性。创建或列出 Kubernetes 对象以及执行 Pod 容器等操作都作为整齐组织的 kubectl 子命令可用-您可以在对集群执行临时操作时使用这些命令,也可以作为应用程序的持续集成/持续部署CI/CD)的一部分使用这些命令。

在本章中,我们将为您提供如何在 Windows 机器上安装 kubectl 以及如何使用 kubectl 管理多个 Kubernetes 集群的更好理解。您还将学习管理 Kubernetes 对象和调试容器化应用程序最常见和有用的 kubectl 命令。

本章包括以下主题:

  • 安装 Kubernetes 命令行工具

  • 访问 Kubernetes 集群

  • 使用开发集群

  • 查看常见的 kubectl 命令

技术要求

本章,您需要安装以下内容:

  • Windows 10 专业版、企业版或教育版(1903 版本或更高版本,64 位)

  • Windows 2.0.0.3 或更高版本的 Docker 桌面版

  • Windows 的 Chocolatey 软件包管理器(chocolatey.org/

  • Azure CLI

有关 Windows 上 Docker 桌面版的安装和系统要求,请参阅第一章*,创建容器

使用 Chocolatey 软件包管理器并非强制,但它可以使安装过程和应用程序版本管理更加容易。安装过程在此处有文档记录:chocolatey.org/install

对于 Azure CLI,您可以在第二章*,*管理容器中的状态**中找到详细的安装说明。

要使用Azure Kubernetes ServiceAKS)进行跟随,您将需要自己的 Azure 帐户和已创建的 AKS 实例。如果您之前没有为前几章创建帐户,您可以在此处阅读有关如何获取个人使用的有限免费帐户的更多信息:azure.microsoft.com/en-us/free/。AKS 集群部署在第四章中进行了介绍,Kubernetes 概念和 Windows 支持。您还可以在该章节中使用提供的 PowerShell 脚本。

您可以从官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter06

安装 Kubernetes 命令行工具

Kubectl 可在多种操作系统上使用,包括 Windows。如果您在 Linux 上使用 kubectl 有经验,您可以期望唯一的区别是安装过程,命令和基本原则保持不变。对于 Windows,您有几种 kubectl 安装选项,如下所示:

在创建本地开发 Kubernetes 集群时,Docker Desktop for Windows 也可以自动安装 kubectl(可执行文件安装在C:\Program Files\Docker\Docker\Resources\bin\kubectl.exe),或者在创建 AKS 集群实例时使用 Azure CLI(使用az aks install-cli命令,在~/.azure-kubectl/kubectl.exe中安装 kubectl)。这可能会与不同位置已安装的 kubectl 实例产生冲突—您可以始终通过使用(Get-Command kubectl).Path命令在 PowerShell 中检查使用哪个 kubectl 安装。切换到不同的 kubectl 安装需要修改PATH环境并确保所需的优先级。

您可以在官方文档中找到所有安装类型的详细说明:kubernetes.io/docs/tasks/tools/install-kubectl/。我们将演示如何使用 Chocolatey 安装 kubectl,因为这是在 Windows 上安装 kubectl 的最简单和最便捷的方式。按照以下步骤进行操作:

  1. 如果您还没有安装 Chocolatey 软件包管理器,您可以在这里找到安装说明:chocolatey.org/install

  2. 以管理员身份打开 PowerShell 窗口,并使用以下命令安装 kubectl:

choco install kubernetes-cli
  1. 如果您需要将 kubectl 升级到最新版本,请使用以下命令:
choco upgrade kubernetes-cli
  1. 验证 kubectl 是否已安装,例如使用以下命令:
kubectl version

根据 Kubernetes 版本支持策略,您应该使用一个在 kube-apiserver 的次要版本(较旧或较新)之内的 kubectl 版本。例如,kubectl 1.15 保证可以与 kube-apiserver 1.14、1.15 和 1.16 一起使用。建议您在可能的情况下使用集群的最新 kubectl 版本。

请注意,通过 Chocolatey 安装的 kubectl 版本有时可能比最新的稳定版本要旧。在这种情况下,如果您需要最新的稳定版本,请按照直接下载 kubectl 二进制文件的说明进行操作。

在下一节中,我们将演示如何组织访问多个 Kubernetes 集群。

访问 Kubernetes 集群

默认情况下,kubectl 使用位于~\.kube\configkubeconfig文件(请注意我们称其为kubeconfig,但文件名为config),在 Windows 机器上会扩展为C:\Users\<currentUser>\.kube\config。这个 YAML 配置文件包含 kubectl 连接到您集群的 Kubernetes API 所需的所有参数。这个配置文件也可以被除 kubectl 之外的其他工具使用,例如Helm

您可以使用KUBECONFIG环境变量或--kubeconfig标志来强制 kubectl 对个别命令使用不同的kubeconfig。对于KUBECONFIG环境变量,可以在运行时指定多个kubeconfig并合并它们。您可以在官方文档中阅读更多关于此功能的信息:kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#merging-kubeconfig-files。请注意,对于 Windows,您应该用分号分隔KUBECONFIG路径,而在 Linux 中,您应该使用冒号。

使用上下文在kubeconfig中协调访问多个 Kubernetes 集群。每个上下文包含以下信息:

  • 集群:Kubernetes API 服务器的地址。

  • 用户:用户的名称,映射到用户凭据(在kubeconfig中指定)。

  • 命名空间:可选地,您可以提供要使用的默认命名空间。

如果您一直在关注之前的章节,我们演示了 Minikube 的安装和 Windows Docker 桌面上的本地 Kubernetes 集群的安装,您已经使用了在安装这些集群时自动添加的上下文。在使用 kubectl 时,始终有一个上下文标记为当前。您可以使用以下命令查看当前上下文:

PS C:\src> kubectl config current-context
minikube

列出kubeconfig中所有可用的上下文可以通过以下方式完成:

PS C:\src> kubectl config get-contexts
CURRENT   NAME                  CLUSTER               AUTHINFO                                                     NAMESPACE
 aks-windows-cluster   aks-windows-cluster   clusterUser_aks-windows-resource-group_aks-windows-cluster
 docker-desktop        docker-desktop        docker-desktop
 docker-for-desktop    docker-desktop        docker-desktop
*         minikube              minikube              minikube

如果您想切换到不同的上下文,例如docker-desktop,执行以下命令:

PS C:\src> kubectl config use-context docker-desktop
Switched to context "docker-desktop".

您可以从命令行手动修改现有上下文或添加自己的上下文。例如,以下命令将添加一个新的上下文docker-desktop-kube-system,它将连接到docker-desktop集群并默认使用kube-system命名空间。

kubectl config set-context docker-desktop-kube-system `
 --cluster docker-desktop `
 --user docker-desktop `
 --namespace kube-system

当您切换到新的上下文并运行任何命令时,例如kubectl get pods,它将针对kube-system命名空间执行。

在任何给定时间,您可以使用 kubectl 命令的--cluster--user--namespace甚至--context标志来覆盖当前上下文设置。

通常,在使用托管的 Kubernetes 提供程序或本地开发工具时,配置文件将作为一个单独的文件提供,通过KUBECONFIG环境变量进行下载和使用,或者直接合并到当前的kubeconfig中作为一个新的上下文(这就是在 AKS 的情况下az aks get-credentials命令所做的)。如果需要,您可以使用以下 PowerShell 命令手动合并kubeconfigs

$env:KUBECONFIG="c:\path\to\config;~\.kube\config"
kubectl config view --raw

该命令的输出可以作为一个新的默认kubeconfig使用——在覆盖默认配置文件之前,您应该验证结果是否有效。您可以使用以下代码片段将默认的kubeconfig覆盖为合并后的配置:

$env:KUBECONFIG="c:\path\to\config;~\.kube\config"
kubectl config view --raw > ~\.kube\config_new
Move-Item -Force ~\.kube\config_new ~\.kube\config

请记住kubeconfig合并优先规则:如果在两个文件中找到相同的键,则第一个文件中的值将获胜。

现在您知道如何使用 kubeconfig 和 kubectl 上下文来管理对 Kubernetes 集群的访问权限,让我们专注于使用开发集群的策略。

使用开发集群

为 Kubernetes 开发应用程序引入了一些在传统开发流水线中不存在的独特挑战。完美的解决方案将是对流水线和流程进行最小的更改,但不幸的是,事情并不像那么简单。首先,您需要维护一个开发 Kubernetes 集群,用于部署、测试和调试您的应用程序。其次,您必须将应用程序容器化并部署到开发集群,可能比在安全的生产集群中具有更大的灵活性和访问权限。

非正式地,对于 Kubernetes 应用程序开发,您有四种模式(概念),如下图所示:

让我们来看看这四种模式:

  • 完全脱机:在完全脱机(本地)环境中,您的开发环境和 Kubernetes 集群托管在本地机器上。这种配置的一个很好的例子是 Minikube 或 Windows 本地 Kubernetes 集群的 Docker 桌面。在这两种情况下,Kubernetes 集群托管在专用的本地虚拟机(VM)中。这种开发工作流程需要构建 Docker 镜像,将它们推送到镜像注册表(本地或远程),并使用 kubectl 部署应用程序。当然,您可以利用集群在本地运行的事实,登录到集群节点并调试 Pod 容器。从 Windows 容器的角度来看,这需要在 Hyper-V VM 上运行完整的混合 Linux/Windows Kubernetes 集群。这种设置需要一台能够运行至少两个 VM 的本地机器:一个用于 Linux 主节点,一个用于 Windows 工作节点。我们将在第七章中部署一个完全功能的混合集群,部署混合本地 Kubernetes 集群

下一章介绍的部署策略可以用于开发和生产集群。这种方法在开发中的缺点是与 Minikube 或其他完全脱机解决方案相比需要大量配置。不幸的是,目前还没有针对混合开发集群的易用的即插即用解决方案 - 如果您需要尽快为开发创建集群,完全托管的 AKS 是最佳选择。

  • 代理:在使用代理环境时,您的 Kubernetes 集群托管在远程机器上(但也可以托管在本地 VM 上!)。开发环境仍然在您的本地机器上,但您配置了双向网络代理,以便您可以运行和调试应用程序,就好像您在集群中的 Pod 内部一样。换句话说,您可以简化开发工作流程,并跳过开发和调试场景的 Docker 开销。这可以通过使用诸如 Telepresence(www.telepresence.io/)之类的工具来实现。不幸的是,目前仅通过 Windows 子系统支持 Windows,这意味着没有本机 Windows 支持。

  • 远程:下一个模式是远程模式,您可以在本地针对远程集群进行开发,这些集群可以托管在您的本地数据中心或作为托管的 Kubernetes 提供。这类似于在完全脱机环境中使用,但您必须注意使用托管 Kubernetes 集群的额外成本以及对 Kubernetes 节点的有限访问。对于 Windows,如果您正在运行 AKS,您将无法登录到 Linux 主节点,但如果您使用裸 Azure VM 上的 AKS 引擎进行部署,您可以访问 Linux 主节点和 Windows 节点。这种环境类型的优势在于您可以利用 Kubernetes 的所有云集成,例如负载均衡器服务或云卷。我们将在第八章中介绍 AKS 引擎部署,部署混合 Azure Kubernetes 服务集群

  • 完全在线:在完全在线模式下,您的开发环境与 Kubernetes 集群一起远程托管。这种方法的良好示例是 Eclipse Che (www.eclipse.org/che/docs/)和 Azure Dev Spaces (docs.microsoft.com/en-us/azure/dev-spaces/about),它与 Visual Studio Code 完全集成。在这一点上,对 Windows 节点的支持仍在开发中,并且需要手动配置(docs.microsoft.com/en-us/azure/dev-spaces/how-to/run-dev-spaces-windows-containers)。将来,这是为 Windows 容器提供无缝 Kubernetes 开发生命周期的最佳选择。我们将在第十二章中介绍 Azure Dev Spaces,Kubernetes 开发工作流程

有许多工具可以提高您的 Kubernetes 应用程序开发效率,并减少在“一切中间又有一个集群”的开销。例如,对于 Windows 支持,您可能希望查看 Azure Draft (draft.sh/),它可以使用为您的应用程序自动生成的 Helm 图表简化开发流程,或者 ksync (ksync.github.io/ksync/),它可用于将本地代码/二进制更改同步到 Pod 容器,无需重新部署。

在下一节中,我们将快速浏览一下您应该掌握的最常见和有用的 kubectl 命令。

查看常见的 kubectl 命令

Kubectl 是一个强大的工具,当与 Kubernetes 集群交互时,它提供了您所需的大部分功能。所有 kubectl 命令都遵循相同的语法,如下面的代码片段所示:

**kubectl [command] [type] [name] [flags]**

**# Example:**
**kubectl get service kube-dns --namespace kube-system** 

[命令][类型][名称][标志]的定义如下:

  • [命令]指定操作,例如getapplydelete

  • [类型]是资源类型(详细列表可以在文档中找到:kubernetes.io/docs/reference/kubectl/overview/#resource-types),以单数、复数或缩写形式(不区分大小写)指定,例如,serviceservicessvc。您可以使用kubectl explain [type]命令找到有关每个资源的更多信息。

  • [名称]确定资源的名称(区分大小写)。如果命令允许省略名称,则操作将应用于给定类型的所有资源。

  • [标志] 指定了额外的标志,这些标志可以是特定于命令的,也可以是全局的 kubectl 命令,例如,--namespace kube-system

您可以随时使用kubectl helpkubectl [command] --help来访问关于每个命令如何工作以及可用标志的全面文档。kubectl 的官方参考资料可以在这里找到:kubernetes.io/docs/reference/generated/kubectl/kubectl-commands

术语资源对象在 Kubernetes 中经常可以互换使用,尽管在考虑 Kubernetes 内部时存在一些差异。对象是 Kubernetes 系统实体(抽象概念),而资源是提供对象表示的实际 RESTful API 资源。

一些命令,如 getcreate,允许您使用 -o--output 标志指定输出格式。例如,您可以使用 -o json 强制使用 JSON 输出格式,或者使用 -o jsonpath=<template> 使用 JSONPath 模板提取信息。这在基于 kubectl 命令实施自动化时特别有用。您可以在这里找到有关输出类型的更多信息:kubernetes.io/docs/reference/kubectl/overview/#output-options

对于 BashZsh,您可以通过使用自动补全(kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion)来提高 kubectl 的生产力。对于 Windows,PowerShell 尚不支持自动补全,但是,如果您使用 Windows Subsystem for Linux 管理 Kubernetes 集群,也可以安装 Bash 自动补全。

创建资源

在第四章中,Kubernetes 概念和 Windows 支持,我们已经解释了 Kubernetes 中 命令式声明式 资源管理背后的思想。简而言之,在使用命令式管理时,您依赖于创建、删除和替换资源的命令(可以将其视为脚本中的命令)。另一方面,在声明式管理中,您只描述资源的期望状态,Kubernetes 将执行所有必需的操作,以将资源的当前状态转换为期望状态。

以命令式方式在 Kubernetes 中创建资源可以使用 kubectl create -f <manifestFile> 命令。对于声明式方式,您必须使用 kubectl apply -f <manifestFile>。请注意,您可以应用 -R 标志并递归处理目录而不是单个文件。让我们在 Linux nginx Pods 的示例部署清单文件上演示这一点,您可以从本书的 GitHub 存储库下载:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter06/01_deployment-example/nginx-deployment.yaml

假设您将清单文件保存为 nginx-deployment.yaml 在当前目录中,使用 PowerShell 执行以下命令来创建 nginx-deployment-example 部署:

kubectl create -f .\nginx-deployment.yaml

您可以直接在 kubectl 中使用清单文件的 URL-例如,kubectl create -f https://raw.githubusercontent.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/master/Chapter06/01_deployment-example/nginx-deployment.yaml。在将其部署到集群之前,请始终验证清单文件的内容,特别是从安全角度考虑。

您可以使用kubectl apply命令来实现相同的效果,如下所示:

kubectl apply -f .\nginx-deployment.yaml

在这一点上,这些命令的行为方式相同:它们只是创建 Deployment。但是现在,如果您修改nginx-deployment.yaml文件,以便将副本的数量增加到 4,请检查kubectl createkubectl apply命令的结果:

PS C:\src> kubectl create -f .\nginx-deployment.yaml
Error from server (AlreadyExists): error when creating ".\\nginx-deployment.yaml": deployments.apps "nginx-deployment-example" already exists

PS C:\src> kubectl apply -f .\nginx-deployment.yaml
deployment.apps/nginx-deployment-example configured

由于已经创建了 Deployment,因此无法通过命令方式创建它-您需要替换它。在声明性的apply命令的情况下,更改已被接受,并且现有的 Deployment 已被扩展为 4 个副本。

对于声明性管理,kubectl 提供了kubectl diff命令,该命令显示了集群中资源的当前状态与清单文件中资源的差异。请注意,您需要在PATH环境变量中拥有diff工具,或者使用任何其他文件比较工具-例如 Meld (meldmerge.org/),并使用KUBECTL_EXTERNAL_DIFF环境变量指定它。将nginx-deployment.yaml中的replicas数量增加到 5,并检查比较结果,如下所示:

$env:KUBECTL_EXTERNAL_DIFF="meld"
kubectl diff -f .\nginx-deployment.yaml

您可以立即在以下截图中看到,如果执行kubectl apply,将受到影响的属性:

一个经验法则是,尽可能坚持使用声明性资源管理,只将命令留给开发/黑客场景。对于 Kubernetes 应用程序的完全声明性管理,请考虑使用带有 Kustomize 的 kubectl。您可以在以下链接了解更多关于这种方法的信息:kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/

删除资源

为了删除资源,您可以使用kubectl delete [type] [name]命令。这也是仍然建议在声明性集群管理中使用的命令之一,因为它更明确。使用带有--prune标志的kubectl apply更危险,因为您可能会意外删除更多资源。

使用以下命令删除nginx-deployment-example部署:

kubectl delete deployment nginx-deployment-example

如果您想删除给定类型的所有资源,可以使用--all标志而不是资源名称。

描述和列出资源

下一个你经常会使用的命令是kubectl get [type] [name],它显示给定类型的资源的详细信息。例如,为了列出当前上下文中默认命名空间中的 Pods,执行以下命令:

kubectl get pods

您可以使用--all-namespaces--namespace=<namespace>全局标志,允许您显示来自其他命名空间的资源,如下面的代码片段所示:

kubectl get pods --all-namespaces

默认情况下,这个命令显示有限的预定义列。您可以使用-o wide标志来查看更多细节,如下所示:

kubectl get pods -o wide

在某些情况下,您会发现观察资源很有用。以下命令列出所有 Pods,并定期刷新视图以获取最新数据:

kubectl get pods --watch

还有一个不同的命令,kubectl describe,它可以用于显示资源的详细信息,如下面的代码片段所示:

kubectl describe pod nginx-deployment-example-7f5cfc59d6-2bvvx

getdescribe命令的区别在于,get显示来自 Kubernetes API 的资源的纯表示,而describe准备了一个包括事件、控制器和其他资源在内的详细描述。

kubectl get支持不同于表格的输出,例如,-o json-o yaml,这对于与其他工具集成或将资源状态转储到文件非常有用,如下面的代码片段所示:

kubectl get pod nginx-deployment-example-7f5cfc59d6-2bvvx -o yaml

如果你需要对输出进行更多处理,你可以使用 JSONPath (github.com/json-path/JsonPath),它集成到 kubectl 中。例如,以下表达式将列出集群中 Pods 中使用的所有容器镜像:

kubectl get pods --all-namespaces -o jsonpath="{.items[*].spec.containers[*].image}"

最后,您可能会发现使用单个命令列出所有命名空间中的所有资源很有用,如下所示:

kubectl get all --all-namespaces

这应该总是给你一个集群中正在发生的事情的很好的概述!

编辑资源

如本节前面提到的,通常不鼓励对 Kubernetes 资源进行命令式编辑。kubectl editkubectl get、打开您喜欢的文本编辑器和对修改后的清单文件进行 kubectl apply 的组合,如下面的代码块所示:

kubectl edit deployment nginx-deployment-example

在 Windows 机器上,此命令将打开 notepad.exe(或任何其他编辑器,如果您指定了 EDITORKUBE_EDITOR 环境变量),显示 nginx-deployment-example 的当前状态。编辑后,保存文件,关闭编辑器,您的更改将被应用。

另一种方法是使用补丁,可以在声明式管理中使用。kubectl patch 通过合并当前资源状态和仅包含修改属性的补丁来更新资源。修补的常见用例是在混合 Linux/Windows 集群中需要强制执行现有 DaemonSet 的节点选择器时。以下 JSON 补丁可用于确保诸如 Flannel 或 kube-proxy 的 DaemonSet 仅在 Linux 节点上运行:

{
    "spec": {
        "template": {
            "spec": {
                "nodeSelector": {
                    "beta.kubernetes.io/os": "linux"
                }
            }
        }
    }
}

也可以使用 YAML 补丁,但不幸的是,由于 PowerShell 转义规则,我们无法为 beta.kubernetes.io/os 选择器演示这一点。在 PowerShell 中,JSON 仍然需要额外的预处理。

为了将此补丁应用到 nginx-deployment-example 部署中,将补丁保存为 linux-node-selector.json 文件,并运行以下命令:

$patch = $(cat .\linux-node-selector.json)
$patch = $patch.replace('"', '\"')
kubectl patch deployment nginx-deployment-example --patch "$patch"

您可以在官方文档中找到有关资源修补和合并类型的更多信息:kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/

运行临时 Pod

在调试场景中,您可能会发现运行临时 Pod 并附加到它很有用。您可以使用 kubectl run 命令执行此操作——请注意,此命令可以生成不同的资源,但除了 Pod 之外,所有生成器都已被弃用。以下代码段将创建一个带有一个 busybox 容器的 busybox-debug Pod,并在容器中运行一个交互式 Bourne shell 会话:

kubectl run --generator=run-pod/v1 busybox-debug -i --tty --image=busybox --rm --restart=Never -- sh

当 shell 提示符出现时,您可以在集群内执行操作,例如对内部 IP 进行 ping。退出 shell 后,容器将被自动删除。

您可以使用类似的方法为 Windows 节点创建交互式 PowerShell Pod。

访问 Pod 容器日志

在调试在 Kubernetes 上运行的应用程序时,容器日志提供了关键信息。您可以使用kubectl logs命令访问 Pod 容器日志,类似于您为 Docker CLI 所做的操作,如下所示:

kubectl logs etcd-docker-desktop -n kube-system

如果 Pod 只运行一个容器,这将起作用。如果 Pod 由多个容器组成,您需要使用--container--all-containers标志。

此外,您可能希望尾随日志的n行(--tail=n标志)并启用日志的实时流(--follow标志),如下面的代码片段所示:

kubectl logs etcd-docker-desktop -n kube-system --tail=10 --follow

进入 Pod 容器

在调试在 Kubernetes 上运行的应用程序时,您可以像裸 Docker 容器一样exec到运行在 Pod 中的容器。例如,要列出容器当前工作目录中的所有文件,请使用以下kubectl exec命令:

kubectl exec nginx-deployment-example-5997d7d5fb-p9fbn -- ls -al

也可以附加交互式终端并运行 Bash 会话,如下所示:

kubectl exec nginx-deployment-example-5997d7d5fb-p9fbn -it bash

对于多容器 Pod,您必须使用--container标志,否则将选择 Pod 中的第一个容器。

复制 Pod 容器文件

Kubectl 为您提供了在您的计算机和 Pod 容器之间复制文件的可能性(双向),类似于 Docker CLI。例如,要将/var/log/dpkg.log文件从运行在nginx-deployment-example-5997d7d5fb-p9fbn Pod 中的容器复制到当前目录,请执行以下kubectl cp命令:

kubectl cp nginx-deployment-example-5997d7d5fb-p9fbn:/var/log/dpkg.log dpkg.log

一般来说,如果您将 Pod 容器用作源或目的地,您需要指定 Pod 名称和容器文件系统路径,用冒号(:)分隔。对于其他命令,如果 Pod 运行多个容器,您需要使用--container标志,否则将选择第一个容器。

端口转发和代理流量

Kubectl 可以充当访问您的 Kubernetes 集群的简单代理。如果需要直接从本地计算机通信到 Pod 上的特定端口,可以使用端口转发到 Pod。这可以通过使用kubectl port-forward命令来实现,而无需手动暴露服务对象。该命令可用于将流量转发到基于其他对象选择器自动选择的 Pod,例如 Deployment,如下所示:

kubectl port-forward deployment/nginx-deployment-example 7000:80

此命令将所有流量从本地计算机端口7000转发到nginx-deployment-example Deployment 中一个 Pod 的端口80。转到http://localhost:7000以验证默认的 nginx 页面是否可访问。完成后终止端口转发命令。

此外,kubectl 可以为您的本地机器提供访问 Kubernetes API 服务器的权限。使用kubectl proxy命令将 API 暴露在端口8080上,如下所示:

kubectl proxy --port=8080

现在,当您在浏览器中导航到http://localhost:8080/api/v1/namespaces/default/pods时,您将看到当前在集群中运行的 Pod 对象。恭喜您成功设置了 kubectl 端口转发!

摘要

在本章中,您已经学会了如何安装和使用 Kubernetes 命令行工具 kubectl。我们已经介绍了如何使用 kubectl 上下文来组织访问多个 Kubernetes 集群,以及处理开发集群的可能策略以及它们如何适用于 Windows 集群。除此之外,您现在还了解了基本的 kubectl 命令以及一些用于调试在 Kubernetes 上运行的应用程序的技术:运行临时 Pod、访问 Pod 容器日志、执行 Pod 容器内部的 exec 操作,以及在本地机器和 Pod 容器之间复制文件的几种技巧。

下一章将重点介绍在本地场景中部署混合 Linux/Windows Kubernetes 集群。我们将演示如何使用 Hyper-V VM 在本地机器上创建一个完全功能的多节点集群。

问题

  1. kubeconfig是什么?

  2. 您如何为 kubectl 设置自定义的kubeconfig位置?

  3. kubectl 中上下文的作用是什么?

  4. kubectl createkubectl apply命令有什么区别?

  5. 什么是 kubectl 资源补丁,以及何时使用它?

  6. 从 Pod 容器中显示实时日志的命令是什么?

  7. 您如何使用 kubectl 在本地机器和 Pod 容器之间复制文件?

您可以在本书的评估部分找到这些问题的答案。

进一步阅读

第三部分:创建 Windows Kubernetes 集群

本节重点讨论围绕创建(配置和部署)混合 Linux/Windows Kubernetes 集群的高级主题,其中包括 Linux 主节点和 Windows 节点。这些章节涵盖了在本地和云(Azure)场景中的部署。

本节包括以下章节:

  • 第七章,部署混合本地 Kubernetes 集群

  • 第八章,部署混合 Azure Kubernetes 服务引擎集群

第七章:部署混合本地 Kubernetes 集群

在之前的章节中,我们更多地从理论角度关注了 Docker 和 Kubernetes 的概念,现在是时候利用这些知识并从头开始部署一个 Kubernetes 集群了。本章的目标是在本地环境中拥有一个功能齐全的混合 Windows/Linux Kubernetes 集群。

根据您的需求,您可以使用这种方法创建一个最小化的本地开发集群(一个充当主节点的 Linux 虚拟机(VM)和一个充当节点的 Windows VM),或者部署一个具有 Linux 和 Windows 节点的生产级本地集群。您不仅限于 Hyper-V 集群——只要设置了适当的网络并且机器能够运行容器化工作负载,这种方法就可以用于裸机、VMware 集群或在云中运行的 VM。使用 kubeadm 创建 Kubernetes 集群可以让您灵活地在任何地方部署集群,只要设置了适当的网络并且机器能够运行容器化工作负载。

我们还建议使用 kubeadm,因为它是一个低级工具,可以深入了解集群的实际创建过程。在未来,您可以期待基于 kubeadm 构建的其他解决方案(如 Kubespray),支持混合集群。但即使如此,仍建议尝试纯 kubeadm 方法来学习 Kubernetes 集群部署的基本步骤。

本章涵盖以下主题:

  • 准备 Hyper-V 环境

  • 使用 kubeadm 创建 Kubernetes 主节点

  • 安装 Kubernetes 网络

  • 为 Windows 节点准备虚拟机

  • 使用 kubeadm 加入 Windows 节点

  • 部署和检查您的第一个应用程序

技术要求

对于本章,您将需要以下内容:

  • Windows 10 专业版、企业版或教育版(1903 版本或更高版本,64 位);至少 16GB RAM 的 Hyper-V 主机(如果选择不安装 Windows Server 和 Ubuntu Server VMs 的桌面体验,则可能更少)。您可以使用任何其他具有 Hyper-V 功能的 Windows 或 Windows Server 版本。对于 Hyper-V,需要在基本输入/输出系统(BIOS)中启用英特尔虚拟化技术(Intel VT)或 AMD 虚拟化(AMD-V)技术功能。

注意:Windows 10 家庭版不能用作 Hyper-V 主机。

您可以从官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter07

准备 Hyper-V 环境

集群部署的第一步是为 Kubernetes 主节点和节点 VM 准备 Hyper-V 主机。

如果您选择使用不同的 hypervisor 或裸机,可以跳过本节。

现在,如果您在之前的章节中已在您的计算机上安装了 Windows 的 Docker Desktop,那么 Hyper-V 已启用和配置。您只需要创建一个内部网络地址转换 (NAT)或外部 Hyper-V 虚拟交换机 (vSwitch),然后您就可以开始了。

以下图表显示了我们将在本章中部署的集群设计。请记住,主节点配置为高可用性HA)- HA 设置与 Windows 容器的支持无关,您可以在准备 Linux 主节点时执行它,根据官方文档(kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/):

最小部署是一个 Ubuntu Server 18.04 LTS Kubernetes 主节点(具有调度应用程序 Pod 的可能性)和一个 Windows Server 2019 LTS Kubernetes 节点。可选地,您可以决定部署更多的 Windows 和 Linux 节点(在图表中用虚线标记),按照相同的说明或克隆 VM。

启用 Hyper-V

首先,让我们启用 Hyper-V 功能,如果您之前没有启用它,如下所示:

  1. 以管理员身份打开 PowerShell 窗口。

  2. 执行以下命令以启用 Hyper-V 功能:

Enable-WindowsOptionalFeature -Online -FeatureName  Microsoft-Hyper-V -All
  1. 重新启动计算机。

如果您正在使用 Windows Server 作为您的 Hyper-V 主机,可以在官方文档中找到启用 Hyper-V 角色的说明:docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/get-started/install-the-hyper-v-role-on-windows-server

现在,根据您的网络设置,您必须创建一个适当的 Hyper-V vSwitch。您有两个选项:

  1. 内部 NAT Hyper-V vSwitch:如果您计划仅将集群用于本地开发,请使用此选项。任何外部入站通信(除了您的 Hyper-V 主机机器)都将需要 NAT。在大多数情况下,此选项适用于简单的 Windows 10 开发设置,因为您连接到不允许您自行管理动态主机配置协议DHCP)和域名系统DNS)的外部网络(以太网或 Wi-Fi)。换句话说,如果您使用外部 vSwitch,您将得到节点的不可预测的 IP 地址分配。没有 DNS,您将无法确保适当的 Kubernetes 集群连接。

  2. 外部 Hyper-V vSwitch:如果您的网络有一个 DHCP 和 DNS 服务器,您(或网络管理员)可以管理,那么请使用此选项。这在大多数生产部署中都是这样。然后,您需要为 VM 分配适当的媒体访问控制MAC)地址,以便获得所需的 IP 地址。

我们将遵循网络的默认网关为10.0.0.1,主节点的 IP 地址为10.0.0.2,节点具有连续的 IP 地址10.0.0.X的约定。

创建内部 NAT Hyper-V vSwitch

为了创建内部 NAT vSwitch,请执行以下步骤:

  1. 以管理员身份打开 PowerShell 窗口。

  2. 执行以下命令创建名为Kubernetes NAT Switch的内部 vSwitch:

New-VMSwitch -SwitchName "Kubernetes NAT Switch" -SwitchType Internal
  1. 找到您刚刚创建的 vSwitch 的ifIndexifIndex将需要用于 NAT 网关的创建。您可以通过运行以下命令来执行此操作:
Get-NetAdapter

以下截图显示了上述命令的输出:

  1. 配置 NAT 网关,如下所示:
New-NetIPAddress -IPAddress 10.0.0.1 -PrefixLength 8 -InterfaceIndex <ifIndex>
  1. 创建新的 NAT 网络Kubernetes NAT Network,如下所示:
New-NetNAT -Name "Kubernetes NAT Network" -InternalIPInterfaceAddressPrefix 10.0.0.0/8

如果您使用内部 NAT vSwitch,则必须为每个 VM 提供静态 IP 地址、网关 IP 地址和 DNS 服务器信息。静态 IP 地址必须在 NAT 内部前缀范围内。

请注意,目前您的系统中只能有一个自定义内部 NAT vSwitch。您可以在官方文档中阅读更多信息:docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/setup-nat-network

创建外部 Hyper-V vSwitch

或者,为了创建外部 vSwitch,请执行以下步骤:

  1. 使用“开始”菜单启动 Hyper-V 管理器。

  2. 从“操作”选项卡中单击“虚拟交换机管理器…”,选择“外部”,然后单击“创建虚拟交换机”。

  3. 使用名称Kubernetes External Switch,并选择用于连接到互联网的网络适配器,例如您的 Wi-Fi 适配器,如下截图所示:

  1. 单击“确定”以接受更改。

现在,Hyper-V 环境准备就绪,您可以继续下一步——在运行 Ubuntu Server 18.04 LTS VM 上创建 Kubernetes 主节点。

使用 kubeadm 创建 Kubernetes 主节点

对于混合 Windows/Linux Kubernetes 集群,您需要部署一个 Linux 主节点——这一步与仅 Linux 的 Kubernetes 集群几乎相同,并且您可以使用任何支持的操作系统来完成此目的。我们选择了 Ubuntu 服务器 18.04 LTS,因为它得到了广泛的支持(官方和社区支持),具有简单的安装过程,并且易于管理。

本章的说明重点是将 Windows 节点添加到 Kubernetes 集群。主节点的准备步骤很少。如果您在本地机器上部署开发集群,使用 kubeadm 在集群中部署单个控制平面就足够了。对于生产部署,您应该考虑部署一个 HA 主节点配置。您可以在以下网址了解有关 HA 和 kubeadm 的更多信息:kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/

如果您还没有下载 Ubuntu 服务器 18.04 LTS 的 ISO 镜像,官方镜像可以在以下网址找到:releases.ubuntu.com/18.04.3/ubuntu-18.04.3-live-server-amd64.iso

创建虚拟机并安装 Ubuntu 服务器

本小节将指导您完成以下步骤,以准备一个带有 Ubuntu 服务器的新虚拟机:

  1. 创建虚拟机

  2. 安装 Ubuntu 服务器

  3. 配置网络

  4. 安装用于与 Hyper-V 集成的额外软件包

  5. 建立一个无密码的安全外壳(SSH)登录

创建虚拟机

首先,您需要创建一个将用作主节点的虚拟机,运行 Ubuntu 服务器 18.04。要做到这一点,打开 Hyper-V 管理器应用程序,并执行以下步骤:

  1. 从“操作”菜单中,选择“新建”,然后点击“虚拟机”。

  2. 点击“下一步”,为主节点虚拟机选择一个名称。我们将用Kubernetes Master来命名。可选地,配置一个自定义目录来存储虚拟机数据,以确保有足够的磁盘空间来托管虚拟机,如下截图所示:

  1. 在下一个对话框中,选择第 2 代并继续,如下截图所示:

  1. 为主节点分配至少2048 MB 的 RAM。您也可以选择使用动态内存分配功能。您可以在官方文档中找到有关硬件最低要求的更多信息,网址为:kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#before-you-begin。对于生产场景,请考虑使用至少16384 MB 的 RAM。下面的截图说明了这个过程:

  1. 选择内部 NAT 或外部交换作为虚拟机的连接,如下截图所示:

  1. 创建一个大小为250 GB 的新虚拟硬盘。由于虚拟硬盘 v2VHDX)是动态可扩展的,因此最好从一开始就分配更多的空间,而不是以后再扩展磁盘和分区。下面的截图说明了这个过程:

  1. 选择从 ISO 安装操作系统,并选择 Ubuntu Server 18.04 LTS 镜像文件,如下截图所示:

  1. 完成虚拟机创建向导。在启动虚拟机之前,我们需要进一步配置它。右键单击 Kubernetes Master VM 并打开设置。

  2. 在安全菜单中,确保安全启动模板设置为 Microsoft UEFI 证书颁发机构,如下截图所示:

  1. 在处理器菜单中,将虚拟处理器的数量设置为至少2,如下截图所示:

  1. 在网络适配器高级功能菜单中,选择为容器启用 MAC 地址欺骗。如果您正在使用外部 vSwitch 并且有外部 DHCP,您可能还想配置静态 DHCP 分配。对于内部 NAT vSwitch,您可以保留默认的动态设置,如下截图所示:

  1. 应用所有更改并启动虚拟机。

如果您考虑完全自动化这个过程,您可以选择 Vagrant(www.vagrantup.com/)用于开发目的,或者 Packer 用于生产场景(www.packer.io/)。使用 Vagrant,您可以轻松地从头开始创建开发 VM,并使用例如 Ansible 自动化配置过程。使用 Packer,您可以为 VM 或云提供商生成机器映像,以便使用基础设施即代码IaC)范例。

安装 Ubuntu 服务器

主节点的虚拟机已经创建,现在我们需要在虚拟机上安装 Ubuntu Server 18.04 LTS。为了做到这一点,请执行以下步骤:

  1. 通过在 Hyper-V 管理器中双击 Kubernetes Master VM 来连接到正在运行的 VM 终端。

  2. 等待安装程序初始化。

  3. 选择英语作为首选语言。

  4. 选择所需的键盘布局。

  5. 在网络连接中,根据您的外部网络配置进行操作:

  • 如果您使用内部 NAT vSwitch,则必须手动设置配置。打开 eth0 接口并编辑 IPv4,选择手动方法。设置子网10.0.0.0/8,地址10.0.0.2,网关10.0.0.1,名称服务器8.8.8.8,8.8.4.4(如果可能,请使用您的提供商的 DNS 服务器地址)。

  • 如果您使用外部 vSwitch,请根据您的要求使用自动配置或手动配置网络。

  1. 可选地,配置网络代理。请记住,如果您在网络代理后运行,您将稍后需要配置 Docker 以使用代理。

  2. 继续使用默认的镜像地址。

  3. 在文件系统设置中,配置为使用整个磁盘。

  4. 选择默认的磁盘进行安装。

  5. 按照建议的文件系统设置进行操作。

  6. 在配置文件设置中,您可以配置机器名称和第一个用户名。我们将使用kubernetes-master作为机器名称,ubuntu作为用户名。

  7. 在 SSH 菜单中,选择安装 OpenSSH 服务器。

  8. 不要选择任何额外的软件包,并继续安装。

  9. 等待安装完成。

  10. 重新启动。

可以使用 Kickstart 或 preseed 配置文件自动安装 Ubuntu 服务器。您可以在官方文档中找到更多信息,网址为:help.ubuntu.com/lts/installation-guide/i386/ch04s06.html。这种方法可以与 Vagrant 或 Packer 一起使用。可以在以下网址找到适用于 Packer 的 Ubuntu Server 18.04 的示例 preseed 配置文件:github.com/ptylenda/ironic-packer-template-ubuntu1804-kubernetes-ansible-proxy/blob/master/http/preseed.cfg

让我们来看看网络配置。

配置网络

如果您正在使用内部 NAT vSwitch 或外部 vSwitch 与外部基于 Windows 的 DHCP 服务器,则在机器重新启动后需要进行一些额外的网络配置,如下所示:

  1. 在 VM 终端窗口中,使用用户名ubuntu和您的密码登录。

  2. 使用vimnano打开以下文件:

sudo vim /etc/netplan/01-netcfg.yaml

如果您不熟悉 Vim 编辑器,我们强烈建议学习基础知识。例如,可以在以下网址找到一个简明指南:eastmanreference.com/a-quick-start-guide-for-beginners-to-the-vim-text-editor。Vim 非常适用于在 Linux 和 Windows 上编辑文件,而无需桌面环境。作为替代,您可以使用 nano (www.nano-editor.org/)。

  1. 如果您正在使用内部 NAT vSwitch,请按以下方式强制执行 Kubernetes 主节点的静态 IP 地址配置:
network:
  ethernets:
    eth0:
      dhcp4: no
      addresses: [10.0.0.2/8]
      gateway4: 10.0.0.1
      nameservers:
        addresses: [8.8.8.8,8.8.4.4]
  version: 2
  1. 或者,如果您正在使用外部 vSwitch 和外部基于 Windows 的 DHCP 服务器,请将文件内容设置为以下内容:
network:
  ethernets:
    eth0:
      dhcp4: yes
      dhcp-identifier: mac
  version: 2

dhcp-identifier设置为mac对于使 DHCP 租约正常工作至关重要。

  1. 保存文件并使用sudo reboot命令重新启动机器。

我们现在将安装一些额外的软件包。

安装与 Hyper-V 集成的额外软件包

对于任何网络配置(包括内部 NAT 和外部 vSwitch),您现在应该安装一些额外的虚拟化工具,以启用与 hypervisors 集成的一些专用功能,如下所示:

  1. 再次登录到机器上。

  2. 通过运行以下命令更新apt-get缓存:

sudo apt-get update
  1. 安装额外的虚拟化工具,如下所示:
sudo apt-get install -y --install-recommends linux-tools-virtual linux-cloud-tools-virtual
  1. 重启。

现在,让我们设置一个无密码 SSH 登录。

设置无密码 SSH 登录

在这一点上,建议使用 SSH 而不是 VM 终端来管理机器。这将需要以下操作:

  1. 在您用于连接到 VM 的 Windows 机器上安装 SSH 客户端(在大多数情况下,您的 VM 主机机器)

  2. 生成 SSH 密钥对以禁用 SSH 的密码身份验证

要在 Windows 机器上安装本机 SSH 客户端,请执行以下步骤:

  1. 以管理员身份打开 PowerShell 窗口

  2. 运行以下命令以获取当前可用版本的 OpenSSH 客户端:

PS C:\WINDOWS\system32> Get-WindowsCapability -Online | ? Name -like 'OpenSSH*'
Name : OpenSSH.Client~~~~0.0.1.0
State : NotPresent

Name : OpenSSH.Server~~~~0.0.1.0
State : NotPresent
  1. 像这样安装客户端:
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
  1. 要连接到 Kubernetes 主节点 VM,您需要知道其 IP 地址。如果您使用静态 IP 地址配置,这相当简单——您使用10.0.0.2。对于由 DHCP 提供的动态 IP 地址,您需要首先确定它。由于在前面的步骤中安装了虚拟化工具,您可以在 Hyper-V 管理器的网络选项卡中轻松找到它,如下面截图底部所示:

  1. 在这种情况下,IP 地址是10.0.0.2,我们可以使用它来 SSH 进入 VM,如下所示:
PS C:\WINDOWS\system32> ssh ubuntu@10.0.0.2
The authenticity of host '10.0.0.2 (10.0.0.2)' can't be established.
ECDSA key fingerprint is SHA256:X6iv9E7Xixl5GFvV+WxiP10Gbkvh1j3xPsBEV/4YcFo.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.0.0.2' (ECDSA) to the list of known hosts.
ubuntu@10.0.0.2's password:
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-65-generic x86_64)

如果遇到连接问题,例如Connection closed by 10.0.0.2 port 22,您需要重新生成 SSH 主机密钥。在 VM 终端中运行sudo ssh-keygen -A,然后尝试重新连接。如果问题仍然存在,请使用sudo service sshd status分析 sshd 服务日志。

下一步是为无密码登录到 Kubernetes 主 VM 生成 SSH 密钥对。要做到这一点,请执行以下步骤:

  1. 打开 PowerShell 窗口。

  2. 运行以下命令生成密钥对。不要指定密码:

ssh-keygen.exe
  1. 现在,您的公钥可在C:\Users\<user>\.ssh\id_rsa.pub中找到。使用以下命令将其复制到 Kubernetes Master VM。此命令确保authorized_keys文件具有适当的安全访问权限:
cat ~/.ssh/id_rsa.pub | ssh ubuntu@10.0.0.2 "cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
  1. 最后一步是测试无密码身份验证,并禁用 SSH 服务器的密码身份验证以获得最佳安全性,如下面的代码片段所示:
ssh ubuntu@192.168.43.105
# You should not be asked for password at this point!
  1. 编辑/etc/ssh/sshd_config,如下所示:
sudo vim /etc/ssh/sshd_config
  1. 找到PasswordAuthentication yes行并将其注释掉,如下所示:
#PasswordAuthentication yes
  1. 保存更改并重新启动 SSH 服务器,如下所示:
sudo service sshd restart
  1. 重新连接以验证您的配置。

在这一点上,最好为Kubernetes Master导出 Hyper-V VM 镜像(或创建检查点)。如果在 Kubernetes 主配置期间出现任何问题,这将使恢复到初始配置变得更容易。

安装和配置 Kubernetes 先决条件

为 Kubernetes 主机(以及 Kubernetes 节点)准备 Ubuntu Server 18.04 LTS 机器需要执行以下步骤:

  1. 更改操作系统配置,例如禁用交换空间。

  2. 安装 Docker 容器运行时。

Ubuntu Server 为 Kubernetes 准备的所有步骤也可以在书籍的官方 GitHub 存储库中作为 bash 脚本找到,网址为:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter07/02_ubuntu-prepare-node.sh

截至 Kubernetes 1.17,经过验证的 Docker 版本列表如下:1.13.1、17.03、17.06、17.09、18.06、18.09、19.03。

为了配置操作系统以运行 Kubernetes,执行以下步骤:

  1. 打开 PowerShell 窗口。

  2. SSH 进入 Kubernetes 主机,如下所示:

ssh ubuntu@10.0.0.2
  1. 更新apt-get并升级所有软件包,如下所示:
sudo apt-get update
sudo apt-get dist-upgrade -y
  1. 安装所需的软件包,如下所示:
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common ebtables ethtool -y 
  1. 禁用当前引导的交换分区,如下所示:
sudo swapoff -a
  1. 永久删除交换分区。编辑sudo vim /etc/fstab并删除任何类型为swap的行,例如以下行:
/swap.img       none    swap    sw      0       0

编辑/etc/fstab应始终使用创建的文件备份执行。此文件中的配置错误可能导致无法引导的系统!

  1. 可选地,重新启动计算机以检查交换分区是否未再次挂载。重新启动计算机后,SSH 进入计算机并检查swap是否已禁用-列表应为空,如下所示:
swapon -s
  1. 确保在系统引导期间加载br_netfilter内核模块。使用sudo vim /etc/modules-load.d/kubernetes.conf命令创建文件并设置以下内容:
br_netfilter
  1. 为 Kubernetes 配置sysctl变量(Flannel 网络所需)。使用sudo vim /etc/sysctl.d/99-kubernetes.conf命令创建一个新文件,并确保文件具有以下内容:
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-arptables = 1
  1. 加载br_netfilter内核模块以进行当前引导,并使用以下命令重新加载sysctl变量:
sudo modprobe br_netfilter
sudo sysctl --system

此时,您的 Ubuntu 服务器虚拟机已准备好安装 Docker 和 Kubernetes。为了安装 Docker 18.09,这是与 Kubernetes 1.16 一起使用的最新验证版本,请执行以下步骤:

  1. 为 Docker apt软件包存储库添加官方GNU 隐私保护GPG)密钥,如下所示:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

Ubuntu 上 Docker 的最新安装说明可以在以下网址找到:docs.docker.com/install/linux/docker-ce/ubuntu/。始终与 Kubernetes 容器运行时安装文档进行交叉检查,因为它包含额外重要信息,可以在以下网址找到:kubernetes.io/docs/setup/production-environment/container-runtimes/#docker

  1. 添加 Docker apt软件包存储库,如下所示:
sudo add-apt-repository \
 "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
 $(lsb_release -cs) \
 stable"
  1. 更新apt-get缓存以刷新存储库的信息,如下所示:
sudo apt-get update
  1. 安装经过 Kubernetes 使用验证的最新 Docker 版本,如下所示:
sudo apt-get install docker-ce=5:18.09.9~3-0~ubuntu-bionic -y
  1. 通过运行以下命令禁用docker-ce软件包的自动更新(这将防止安装未经验证的 Docker 版本):
sudo apt-mark hold docker-ce
  1. 为 Kubernetes 配置 Docker 守护程序。使用sudo vim /etc/docker/daemon.json命令创建一个新文件,并设置以下内容:
{
 "exec-opts": ["native.cgroupdriver=systemd"],
 "log-driver": "json-file",
 "log-opts": {
 "max-size": "100m"
 },
 "storage-driver": "overlay2"
}
  1. 重新加载systemctl并重新启动 Docker 守护程序,使用以下命令:
sudo systemctl daemon-reload
sudo systemctl restart docker
  1. 验证 Docker 是否已正确安装,方法是运行hello-world容器,如下所示:
ubuntu@kubernetes-master:~$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete Digest: sha256:c3b4ada4687bbaa170745b3e4dd8ac3f194ca95b2d0518b417fb47e5879d9b5f
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

如果您在网络代理后面工作,您应该确保您有以下内容:

您的 Ubuntu 服务器虚拟机现在已准备好作为 Kubernetes 主节点进行初始化。首先,在安装 Kubernetes 二进制文件之前,让我们对集群进行初始规划。

规划集群

在开始初始化集群之前,您需要确定 Kubernetes 组件将使用的特定子网和地址范围。这取决于您的外部网络设置(例如,避免任何 IP 地址冲突)以及您计划在集群中运行的 Pod 和服务的数量。一般来说,特别是在本地机器后面的内部 NAT vSwitch 上运行的开发集群,使用默认值是一个好主意。您需要从表中确定以下值用于您的集群:

描述 默认值
服务子网 用于 Pod 访问服务的虚拟子网(不可路由)。节点上运行的kube-proxy执行从虚拟 IP 到可路由地址的地址转换。 10.96.0.0/12
集群(Pod)子网 集群中所有 Pod 使用的全局子网。一般来说,使用 Flannel 时,每个节点被分配一个较小的/24 子网用于其 Pod。请记住,这个子网必须足够大,以容纳集群中运行的所有 Pod。 10.244.0.0/16
Kubernetes DNS 服务 IP 用于集群服务发现和域名解析的kube-dns服务的 IP 地址。 10.96.0.10

这些值将在初始化集群的下一步骤中需要。

初始化集群

为了初始化 Kubernetes 的 Ubuntu 主节点并加入 Windows 节点,我们将使用 kubeadm——在 Kubernetes 1.16(以及 1.17)中,这是唯一一个用于混合 Windows/Linux 集群的自动化部署方法。第一步是在 Ubuntu 服务器 VM 上安装 kubeadm、kubelet 和 kubectl。这也在 GitHub 存储库中的脚本中有所涵盖,链接为:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter07/03_ubuntu-install-kubeadm.sh

安装 kubeadm 和初始化 Kubernetes 主节点的官方说明可以在以下链接找到:kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/

SSH 进入 Ubuntu 服务器 VM,并执行以下步骤:

  1. 按照以下步骤添加 Kubernetes apt 软件包存储库的 GPG 密钥:
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
  1. 添加 Kubernetes apt 软件包存储库。使用sudo vim /etc/apt/sources.list.d/kubernetes.list命令创建一个新文件,并设置以下内容(请注意,您目前必须使用kubernetes-xenial存储库,因为bionic目前还不可用):
deb https://apt.kubernetes.io/ kubernetes-xenial main
  1. 更新apt-get并安装所需的软件包,如下:
sudo apt-get update
sudo apt-get install kubelet kubeadm kubectl -y
  1. 通过运行以下代码验证最新的 Kubernetes 版本是否已安装:
ubuntu@kubernetes-master:~$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.1", GitCommit:"d647ddbd755faf07169599a625faf302ffc34458", GitTreeState:"clean", BuildDate:"2019-10-02T16:58:27Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}
  1. 通过运行以下命令禁用 Kubernetes 软件包的自动更新(这一点尤为重要,因为任何 Kubernetes 组件的升级都应该经过深思熟虑并以受控的方式进行,考虑到所有的兼容性问题):
sudo apt-mark hold kubelet kubeadm kubectl

到目前为止,初始化 Kubernetes 主节点和节点的步骤完全相同。当向集群添加更多专用的 Ubuntu 节点或克隆您的 VM 时,您可以按照相同的步骤进行。如果决定克隆机器,请记住确保每个节点的主机名、MAC 地址和product_uuid都是唯一的。在官方文档中了解更多关于如何确保这一点的信息,网址为:kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#verify-the-mac-address-and-product-uuid-are-unique-for-every-node

现在,我们准备使用 kubeadm 初始化集群。为了做到这一点,请执行以下步骤:

  1. 执行以下命令,假设服务网络为10.96.0.0/12,Pod 网络为10.244.0.0/16
sudo kubeadm init --service-cidr "10.96.0.0/12" --pod-network-cidr "10.244.0.0/16"
  1. 仔细检查 kubeadm 初始化输出,并记录kubeadm join信息,如下:
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

 mkdir -p $HOME/.kube
 sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
 sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
 https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 10.0.0.2:6443 --token c4kkga.50606d1zr7w0s2w8 \
 --discovery-token-ca-cert-hash sha256:44b2f0f05f79970cc295ab1a7e7ebe299c05fcbbec9d0c08133d4c5ab7fadb0b
  1. 如果您的 kubeadm 令牌过期(24 小时后),您可以始终使用以下命令创建一个新的:
kubeadm token create --print-join-command
  1. kubectl config (kubeconfig)复制到默认位置,如下:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
  1. 现在,建议将配置复制到 Windows 机器,以便能够在不登录到主节点的情况下管理集群。在 PowerShell 窗口中,执行以下命令:
scp ubuntu@10.0.0.2:.kube/config config
$env:KUBECONFIG="config;$env:USERPROFILE\.kube\config"
kubectl config view --raw
  1. 仔细检查合并的配置,以确保您没有覆盖任何现有集群的配置。您可以在第六章中了解有关合并kubeconfigs的更多信息,与 Kubernetes 集群交互。如果合并的配置正确,您可以将其保存为$env:USERPROFILE\.kube\config,并使用以下命令切换到kubernetes-admin@kubernetes上下文:
$env:KUBECONFIG="config;$env:USERPROFILE\.kube\config"
kubectl config view --raw > $env:USERPROFILE\.kube\config_new 
Move-Item -Force $env:USERPROFILE\.kube\config_new $env:USERPROFILE\.kube\config

kubectl config use-context "kubernetes-admin@kubernetes"
  1. 验证配置是否正常工作。按照以下方式检索节点列表(请注意,NotReady状态是由于尚未安装 Pod 网络):
PS C:\src> kubectl get nodes
NAME                STATUS     ROLES    AGE   VERSION
kubernetes-master   NotReady   master   22m   v1.16.1
  1. 如果您不打算添加任何 Ubuntu 节点,可以选择untaint主节点,以允许在主节点上调度 Linux Pods。请注意,这仅适用于开发集群。通过运行以下代码来实现:
kubectl taint nodes --all node-role.kubernetes.io/master-

如果您想重新设置集群,首先需要使用 kubeadm 拆除集群。在官方文档中了解更多关于此过程的信息:kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#tear-down

您的 Kubernetes 主节点几乎准备就绪。最后一步是安装 Pod 网络。让我们继续!

安装 Kubernetes 网络

在使用 kubeadm 初始化 Kubernetes 主节点之后,下一步是安装 Pod 网络。我们在第五章中介绍了 Kubernetes 网络选项,详细解释了混合 Windows/Linux 集群支持的容器网络接口CNI)插件。对于本地部署的集群,我们将使用 Flannel 网络和host-gw后端(Windows 节点上的win-bridge CNI 插件)。请记住,只有在节点之间存在第 2 层L2)连接性(没有第 3 层L3)路由)时,才能使用此方法。一般来说,host-gw后端更可取,因为它处于稳定的功能状态,而覆盖后端对于 Windows 节点仍处于 alpha 功能状态。

如果您对使用覆盖后端的 Flannel 安装感兴趣,请参考官方文档中的详细步骤:kubernetes.io/docs/setup/production-environment/windows/user-guide-windows-nodes/#configuring-flannel-in-vxlan-mode-on-the-linux-control-plane。请注意,您需要安装了 KB4489899 补丁的 Windows Server 2019 以进行覆盖网络。

要安装带有host-gw后端的 Flannel,请执行以下步骤(在 PowerShell 窗口中或通过 SSH 在 Kubernetes 主节点上):

  1. 下载 Flannel for Kubernetes 的最新官方清单文件,如下所示:
# Bash
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# Powershell
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml -OutFile kube-flannel.yml
  1. 自定义清单,使net-conf.json文件部分具有host-gw后端类型和正确定义的 Pod 网络(默认值:10.244.0.0/16),如下所示:
net-conf.json: |
 {
 "Network": "10.244.0.0/16",
 "Backend": {
 "Type": "host-gw"
 }
 }
  1. 应用修改后的清单,如下所示:
kubectl apply -f kube-flannel.yml
  1. 最新的官方清单不需要额外的补丁来进行仅 Linux 调度,因为它已经涵盖了这一要求。如果您遵循官方指南,可以跳过此步骤。

  2. 验证 Pod 网络安装是否成功。您应该能够安排一个运行 Bourne shell 的简单交互式 Pod——只有在您有一个未被污染的主节点用于 Pod 调度或者有其他 Linux 节点时才能工作。通过运行以下代码来实现这一点:

PS C:\src> kubectl run --generator=run-pod/v1 busybox-debug -i --tty --image=busybox --rm --restart=Never -- sh
If you don't see a command prompt, try pressing enter.
/ #

Kubernetes 主节点完全初始化后,我们可以继续为 Windows 节点准备 VM。

为 Windows 节点准备 VM

本节的结构与 Ubuntu Server VM 准备类似。对于 Windows VM,我们将执行以下步骤:

  1. 创建 VM

  2. 安装 Windows Server 2019

  3. 配置网络

  4. 安装 SSH 服务器

  5. 安装和配置 Kubernetes 先决条件

创建 VM

创建 Windows Server 2019 VM 的步骤几乎与 Ubuntu Server 18.04 相同。如果您对该过程的截图感兴趣,请参考前面的部分。

要创建 Windows Server 2019 Kubernetes 节点 VM,请打开 Hyper-V 管理器应用程序并执行以下步骤:

  1. 从“操作”菜单中,选择“新建”,然后单击“虚拟机”。

  2. 点击“下一步”,选择 Windows 节点 VM 的名称。我们将使用Kubernetes Windows Node 01来命名。可选地,配置一个自定义目录来存储 VM 数据,以确保有足够的磁盘空间来托管 VM。每个节点至少需要 30GB 的磁盘空间。

  3. 在下一个对话框中,选择“第二代”并继续。

  4. 为 Windows 节点分配至少 4096MB 的 RAM。使用更少的内存可能会导致偶尔报告KubeletHasInsufficientMemory并阻止 Pod 的调度。由于我们将为此机器启用嵌套虚拟化,动态内存分配功能将不起作用。对于生产场景,考虑分配更多资源。

  5. 选择内部 NAT 或外部交换机作为 VM 的连接。这必须是您用于主节点的相同交换机。

  6. 创建一个新的虚拟硬盘,大小为 250GB 或更大。由于 VHDX 是动态可扩展的,因此最好从一开始就分配更多的空间,而不是以后再扩展磁盘和分区。

  7. 选择从 ISO 安装操作系统,并选择您的 Windows Server 2019(或 1903)镜像文件。

  8. 完成 VM 创建向导。在启动 VM 之前,我们需要进一步配置它。右键单击Kubernetes Windows Node 01VM 并打开“设置”。

  9. 在“处理器”菜单中,将“虚拟处理器数量”设置为至少 2。

  10. 在“网络适配器高级功能”菜单中,选择为容器启用 MAC 地址欺骗。如果您使用外部 vSwitch 并且有外部 DHCP,您可能还想配置静态 DHCP 分配。对于内部 NAT vSwitch,您可以保留默认的“动态”设置。

  11. 应用所有更改。

  12. 在以管理员身份运行的 PowerShell 窗口中使用以下命令启用嵌套虚拟化。

Set-VMProcessor -VMName "Kubernetes Windows Node 01" -ExposeVirtualizationExtensions $true

该机器现在已准备好启动操作系统安装。

安装 Windows Server 2019

Windows Server 2019 的安装过程是使用图形界面执行的。如果您考虑自动化安装过程,例如对于 Vagrant 或 Packer,您应该考虑使用虚拟软驱提供的Autounattend.xml文件。您可以在 GitHub 上找到这样一个配置文件的示例,网址为:github.com/ptylenda/kubernetes-for-windows/blob/master/packer/windows/http/Autounattend.xml

执行以下步骤来执行安装:

  1. 通过在 Hyper-V 管理器中双击Kubernetes Windows Node 01 VM来连接。

  2. 启动虚拟机,并立即按任意键以从安装到虚拟机中的安装 DVD 启动。

  3. 选择语言和区域设置。

  4. 点击立即安装。

  5. 提供安装产品密钥。

  6. 在下一个对话框中,您可以选择是否安装桌面体验。我们建议不安装它,因为这样可以使安装更紧凑,并且将配置留给命令行,这对于自动化更好。

  7. 阅读并接受许可条款。

  8. 选择 Windows Server 的自定义安装。

  9. 继续使用默认的安装目标(整个磁盘,无需分区)。

  10. 等待安装完成并等待机器重新启动。

  11. 在第一次登录时,您必须设置管理员密码。

现在,您已经启动并运行了一个 Windows Server 2019 虚拟机,但在加入 Kubernetes 集群之前,我们需要配置网络并安装先决条件。

配置网络

只有在运行内部 NAT vSwitch 时才需要进行额外的网络配置——在这种情况下,您需要配置静态 IP 地址、网关地址和 DNS 服务器信息。如果您正在运行具有外部 DHCP 的外部 vSwitch,则配置应自动执行。

在本指南中,我们遵循 Kubernetes 节点具有以10.0.0.3开头的连续 IP 地址的约定。为了将10.0.0.3配置为集群中第一个节点的静态 IP 地址,请执行以下步骤:

  1. 通过在虚拟机上运行powershell命令来启动 PowerShell。

  2. 执行以下命令以查找主Ethernet接口的ifIndex

Get-NetAdapter
  1. 为接口创建一个新的静态 IP 地址10.0.0.3,如下所示:
New-NetIPAddress –IPAddress 10.0.0.3 -DefaultGateway 10.0.0.1 -PrefixLength 8 -InterfaceIndex <ifIndex>
  1. 为接口设置 DNS 服务器信息,如下所示(如果需要,请使用适当的 DNS 服务器):
Set-DNSClientServerAddress –InterfaceIndex <ifIndex> –ServerAddresses 8.8.8.8,8.8.4.4

如果您在网络代理后面,可以使用 PowerShell 中的以下命令在机器级别定义适当的环境变量:

[Environment]::SetEnvironmentVariable("HTTP_PROXY", "http://proxy.example.com:80/", [EnvironmentVariableTarget]::Machine)

[Environment]::SetEnvironmentVariable("HTTPS_PROXY", "http://proxy.example.com:443/", [EnvironmentVariableTarget]::Machine)

现在让我们看看如何远程访问 Windows Server 虚拟机。

安装 SSH 服务器

现在,我们需要一种连接到 VM 而不使用 Hyper-V 终端的方法——如果您愿意,仍然可以使用它,但与使用远程桌面协议RDP)或 SSH 相比,它的功能更有限。您有以下选项:

  1. 安装 SSH 服务器并使用 Vim 来管理配置文件。

  2. 启用 RDP 连接(示例指南:theitbros.com/how-to-remotely-enable-remote-desktop-using-powershell/)。

  3. 使用 PowerShell 远程连接(示例指南:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/enable-psremoting?view=powershell-6)。

我们将演示如何在 Windows Server 2019 上启用第一个选项,即 SSH 服务器和 Vim。此选项使得访问我们的 Kubernetes 集群变得统一,您可以在所有节点上使用相同的 SSH 密钥。执行以下步骤:

  1. 在 Windows Server 机器的 Hyper-V 终端连接中,通过使用powershell命令启动 PowerShell。

  2. 通过运行以下代码验证当前可以安装的 SSH 服务器的版本:

Get-WindowsCapability -Online | ? Name -like 'OpenSSH*'

Name : OpenSSH.Client~~~~0.0.1.0
State : NotPresent

Name : OpenSSH.Server~~~~0.0.1.0
State : NotPresent
  1. 安装OpenSSH.Server功能,如下所示:
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
  1. 启动sshd服务,如下所示:
Start-Service sshd
  1. 启用sshd服务的自动启动,如下所示:
Set-Service -Name sshd -StartupType 'Automatic'
  1. 确保适当的防火墙规则已经就位(OpenSSH-Server-In-TCP),如下所示:
Get-NetFirewallRule -Name *ssh*
  1. 如果不存在,请手动添加,如下所示:
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
  1. 从开发机器上,验证通过 SSH 连接到10.0.0.3的 VM 是否可行,如下所示:
PS C:\src> ssh Administrator@10.0.0.3
The authenticity of host '10.0.0.3 (10.0.0.3)' can't be established.
ECDSA key fingerprint is SHA256:VYTfj0b1uZmVgHu9BY17q1wpINNEuzb4dsSGtMFQKw4.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.0.0.3' (ECDSA) to the list of known hosts.
Administrator@10.0.0.3's password:
Microsoft Windows [Version 10.0.17763.737]
(c) 2018 Microsoft Corporation. All rights reserved.

administrator@WIN-GJD24M0P8DA C:\Users\Administrator>
  1. 默认情况下,启动具有有限功能的cmd shell。通过使用powershell命令在 SSH 会话中启动 PowerShell。

  2. 将 SSH 的默认 shell 更改为powershell,使用以下命令:

New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force
  1. 安装 Chocolatey 软件包管理器以安装 Vim 编辑器,如下所示:
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
  1. 使用 Chocolatey 安装 Vim,如下所示:
 choco install vim -y
  1. 配置无密码 SSH 登录。使用vim C:\ProgramData\ssh\administrators_authorized_keys命令将您的~/.ssh/id_rsa.pub公共 SSH 密钥添加到 Windows Server VM 上的administrators_authorized_keys

  2. 修复administrators_authorized_keys文件的权限,如下所示:

icacls C:\ProgramData\ssh\administrators_authorized_keys /remove "NT AUTHORITY\Authenticated Users"
icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r
  1. 重新启动sshd服务,如下所示:
Restart-Service -Name sshd -Force

所有 Windows 配置操作都可以通过Autounattend.xml自动化 Windows 设置(使用常规的cmdpowershell脚本)和在 Windows 主机上支持的 Ansible 的混合来执行。您可以在此最小示例存储库中检查 Packer 如何使用此方法,网址为:github.com/ptylenda/ironic-packer-template-windows2016

此时,您的 Windows Server VM 已连接到网络,并准备安装 Kubernetes 的先决条件。

安装和配置 Kubernetes 的先决条件

首先,确保 Windows Server 2019 已经更新。为了做到这一点,使用 Hyper-V 终端连接并执行以下步骤:

如果您不想使用第三方模块来管理更新,可以使用sconfig命令。目前,这些操作无法通过 SSH 轻松执行,因为它们需要图形用户界面GUI)交互。

  1. 打开 PowerShell 会话,使用powershell命令。

  2. 安装用于管理 Windows 更新的PSWindowsUpdate自定义模块,如下所示:

Install-Module -Name PSWindowsUpdate
  1. 通过运行以下代码触发 Windows 更新(此过程可能需要一些时间才能完成):
Get-WUInstall -AcceptAll -Install

下一步是安装 Docker 和 Kubernetes 本身。可以通过两种方式来实现:

我们将使用第二个选项,因为这是一个更近期的方法,与 kubeadm 支持 Windows 节点一致,从版本 1.16 开始可用。sig-windows-tools脚本执行以下操作:

  1. 启用 Windows Server 容器功能。

  2. 下载所选的容器运行时(Docker 或容器运行时接口CRI))。

  3. 拉取所需的 Docker 镜像。

  4. 下载 Kubernetes 和 Flannel 二进制文件,安装它们,并将它们添加到$env:PATH变量中。

  5. 下载所选的 CNI 插件。

要在 Windows 上安装 Kubernetes 的所有先决条件,请执行以下步骤:

  1. SSH 进入 Windows Server 节点 VM,如下所示:
ssh Administrator@10.0.0.3
  1. 创建并使用一个新目录,其中将下载sig-windows-tools脚本,例如sig-windows-tools-kubeadm,如下所示:
mkdir .\sig-windows-tools-kubeadm
cd .\sig-windows-tools-kubeadm
  1. 下载最新的sig-windows-tools存储库并解压它。请注意,存储库中的路径可能会更改,因为它目前专门用于v1.15.0(您可以在官方文档中查看最新版本,网址为:kubernetes.io/docs/setup/production-environment/windows/user-guide-windows-nodes/#preparing-a-windows-node)。或者,您可以使用书中 GitHub 存储库中的分支:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter07/07_sig-windows-tools-kubeadm。这些脚本包含了一些从sig-windows-tools中挑选出来的修复 bug,以确保网络正常工作。这一步的代码可以在下面的片段中看到:
Invoke-WebRequest -Uri https://github.com/kubernetes-sigs/sig-windo
ws-tools/archive/master.zip -OutFile .\master.zip
tar -xvf .\master.zip --strip-components 3 sig-windows-tools-master/kubeadm/v1.15.0/*
Remove-Item .\master.zip
  1. 现在,您需要自定义Kubeclusterbridge.json文件。这个配置文件是由一个辅助的 PowerShell 模块使用的,它安装先决条件并加入 Windows 节点。在下面的代码块中,您可以找到 Windows Server 2019 节点的配置。您也可以从书的 GitHub 存储库下载它:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter07/07_sig-windows-tools-kubeadm/Kubeclusterbridge.json。您需要确保Images的版本与您的节点操作系统版本匹配,并且Network有适当的ServiceCidrClusterCidr。此外,您需要提供一个KubeadmTokenKubeadmCAHash,这是在初始化 Kubernetes 主节点时生成的。您可以使用kubeadm token create --print-join-command命令在 Kubernetes 主节点上生成一个新的令牌。这一步的代码可以在下面的片段中看到:
{
    "Cri" : {
       "Name" : "dockerd",
        "Images" : {
            "Pause" : "mcr.microsoft.com/k8s/core/pause:1.2.0",
            "Nanoserver" : "mcr.microsoft.com/windows/nanoserver:1809",
            "ServerCore" : "mcr.microsoft.com/windows/servercore:ltsc2019"
        }
    },
    "Cni" : {
        "Name" : "flannel",
        "Source" : [{ 
            "Name" : "flanneld",
            "Url" : "https://github.com/coreos/flannel/releases/download/v0.11.0/flanneld.exe"
            }
        ],
        "Plugin" : {
            "Name": "bridge"
        },
        "InterfaceName" : "Ethernet"
    },
    "Kubernetes" : {
        "Source" : {
            "Release" : "1.16.1",
            "Url" : "https://dl.k8s.io/v1.16.1/kubernetes-node-windows-amd64.tar.gz"
        },
        "ControlPlane" : {
            "IpAddress" : "10.0.0.2",
            "Username" : "ubuntu",
            "KubeadmToken" : "<token>",
            "KubeadmCAHash" : "<discovery-token-ca-cert-hash>"
        },
        "KubeProxy" : {
            "Gates" : "WinDSR=true"
        },
        "Network" : {
            "ServiceCidr" : "10.96.0.0/12",
            "ClusterCidr" : "10.244.0.0/16"
        }
    },
    "Install" : {
        "Destination" : "C:\\ProgramData\\Kubernetes"
    }
}
  1. 此时,您需要切换到 RDP 连接或 Hyper-V 终端连接。安装脚本需要一些交互和无法通过 SSH PowerShell 会话执行的提升权限。

  2. 使用powershell命令启动 PowerShell 会话,转到.\sig-windows-tools-kubeadm目录,并开始安装过程,如下所示:

cd .\sig-windows-tools-kubeadm
.\KubeCluster.ps1 -ConfigFile .\Kubeclusterbridge.json -Install
  1. 在安装过程中,机器将需要重新启动,重新登录后安装将继续。通过检查以下截图中显示的信息,验证加载的配置是否符合预期:

  1. 您可能会遇到完全运行的 Docker 守护程序和正在拉取的镜像之间的竞争条件。如果出现错误,只需再次重启或使用相同命令重新启动安装过程——请记住在下一次尝试之前关闭现有的 PowerShell 会话并启动一个新的会话。根据您的网络连接情况,下载镜像可能需要一些时间。

  2. 镜像已经被拉取,Kubernetes、Flannel 和 CNI 插件已经安装完毕,接下来将会要求您为从新的 Windows 节点访问主节点生成新的 SSH 密钥对——或者您也可以自己操作或重用现有的密钥对。这将使加入过程更加简单,因为加入脚本需要使用 SSH 来检索集群配置。在10.0.0.2主节点上执行脚本输出的命令,以将公钥添加到 Ubuntu 用户的authorized_keys中,如下截图所示:

  1. 安装完成后,关闭 PowerShell 窗口并打开一个新窗口,使用powershell命令。

  2. 目前,您需要清理ContainerBootstrap调度程序任务,该任务在每次启动后重新运行脚本(这可能在未来的版本中得到修复),操作如下:

 Unregister-ScheduledTask -TaskName "ContainerBootstrap"
  1. 使用docker images命令验证 Docker 镜像是否已被拉取,并通过运行kubectl version命令验证 Kubernetes 二进制文件是否已安装。

考虑导出 VM 镜像或创建检查点——如果您选择向集群添加更多的 Windows 节点或在加入过程中遇到问题,这将非常有用。现在我们终于可以将第一个 Windows 节点加入集群了!

使用 kubeadm 加入 Windows 节点

下一个任务是将我们的 Windows Server 2019 VM 作为 Kubernetes 集群中的节点加入。我们将使用来自 Kubernetes sig-windows-tools存储库的相同KubeCluster.ps1脚本,该脚本在内部使用kubeadm加入集群。该脚本将执行以下操作:

  1. 使用 SSH 从10.0.0.2主节点检索kubeconfig文件。

  2. kubelet注册为 Windows 服务。这将确保 kubelet 进程始终在 Windows 节点上运行。

  3. 准备 CNI 插件的配置。

  4. 创建主机网络服务HNS)网络。

  5. 添加防火墙规则(如果需要)。

  6. flanneldkube-proxy注册为 Windows 服务。

如果加入脚本失败,请启动新的 PowerShell 会话并重新运行脚本。

要加入 Windows 节点,请按以下步骤进行:

  1. 在 Windows Server VM 的 Hyper-V 终端中,使用powershell命令启动新的 PowerShell 会话。

  2. 通过运行以下命令导航到带有sig-windows-tools脚本的目录:

cd .\sig-windows-tools-kubeadm
  1. 执行加入命令,就像这样:
.\KubeCluster.ps1 -ConfigFile .\Kubeclusterbridge.json -Join

如果kubeadm join命令出现任何问题(例如,挂起的预检查),您可以编辑KubeClusterHelper.psm1文件,找到kubeadm join命令,并添加--v=3参数(或任何其他详细程度)以获得更详细的信息。此外,您可以检查C:\ProgramData\Kubernetes\logs目录中的服务日志。还可以验证问题是否已知,网址为github.com/kubernetes-sigs/sig-windows-tools/issues——修复可能已经可用。

  1. 加入新的 Windows 节点是一个相对快速的过程,几秒钟后,操作应该完成。现在,验证新节点是否在集群中可见,并且具有Ready状态,就像这样:
PS C:\src> kubectl get nodes
NAME                STATUS   ROLES    AGE   VERSION
kubernetes-master   Ready    master   26h   v1.16.1
win-gjd24m0p8da     Ready    <none>   11m   v1.16.1
  1. 在 Windows 节点上,使用ipconfig命令验证 Flannel 是否已创建cbr0_ep接口,如下面的屏幕截图所示:

  1. 通过创建一个临时的 PowerShell Pod 命名为powershell-debug来对新节点进行快速的烟雾测试。 Pod 规范覆盖必须包含nodeSelector,与 Windows 节点匹配,如下面的代码块所示:
kubectl run `
 --generator=run-pod/v1 powershell-debug `
 -i --tty `
 --image=mcr.microsoft.com/powershell:nanoserver-1809 `
 --restart=Never `
 --overrides='{\"apiVersion\": \"v1\", \"spec\": {\"nodeSelector\": { \"beta.kubernetes.io/os\": \"windows\" }}}'
  1. 镜像拉取可能需要一些时间。您可以使用以下命令观察 Pod 事件:
kubectl describe pod powershell-debug
  1. 当 Pod 启动时,请验证 DNS 解析和对外部地址的连接 - 例如,通过使用 ping google.com 命令,如下截图所示:

  1. 退出容器,然后通过运行以下命令删除 Pod(我们没有使用 --rm 标志,以便您可以轻松使用 kubectl describe 命令调查任何问题):
kubectl delete pod powershell-debug

为了完整起见,为了删除 Windows 节点并重置机器的状态(例如,在配置更改后,为了重新安装和加入),使用相同的 KubeCluster.ps1 脚本并执行以下命令:

.\KubeCluster.ps1 -ConfigFile .\Kubeclusterbridge.json -Reset

恭喜 - 现在,您拥有一个完全功能的混合 Windows/Linux Kubernetes 集群正在运行!您可以选择按照相同的说明或使用 VM 镜像添加更多的 Windows 或 Linux 节点(记得重新生成主机名、MAC 地址和 product_uuids)。

部署和检查您的第一个应用程序

现在,是时候用新创建的 Kubernetes 集群玩一些游戏了。我们将创建一个最小的 Deployment 与 NodePort Service,将应用程序暴露给用户。应用程序本身是官方的 ASP.NET Core 3.0 示例,打包为 Docker 镜像 - 您可以随意使用任何其他 Windows web 应用程序容器镜像,或者创建您自己的镜像。我们选择了官方示例,以便尽快进行部署,以便我们可以专注于 Kubernetes 操作。

要部署示例应用程序,请执行以下步骤:

  1. 创建一个包含 Deployment 和 Service 定义的 windows-example.yaml 清单文件。您可以从 GitHub 仓库 (raw.githubusercontent.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/master/Chapter07/09_windows-example/windows-example.yaml) 下载它,或者直接将其应用到集群,如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: windows-example
  labels:
    app: sample
spec:
  replicas: 3
  selector:
    matchLabels:
      app: windows-example
  template:
    metadata:
      name: windows-example
      labels:
        app: windows-example
    spec:
      nodeSelector:
        "beta.kubernetes.io/os": windows
      containers:
      - name: windows-example
        image: mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809
        resources:
          limits:
            cpu: 1
            memory: 800M
          requests:
            cpu: .1
            memory: 300M
        ports:
          - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: windows-example
spec:
  type: NodePort
  ports:
  - protocol: TCP
    port: 80
    nodePort: 31001
    targetPort: 80
  selector:
    app: windows-example

在此清单文件中有三个重要的点,已用粗体标记出来:

    • 为 Windows 节点进行调度 需要使用带有值为 "beta.kubernetes.io/os": windowsnodeSelector。同样,如果您需要为 Linux 节点调度 Pods,在混合集群中应该使用带有值为 "beta.kubernetes.io/os": linux 的节点选择器。
  • Pod 定义包括一个基于mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809镜像的容器。确保容器主机操作系统版本与容器基础镜像版本之间的兼容性非常重要。在这种情况下,Windows Server 2019 LTS 与基于 1809 的镜像兼容。如果您选择使用 Windows Server 1903 节点,则必须使用基于 1903 的镜像。

  • NodePort Service将在集群中的每个节点上的端口31001上公开。换句话说,您可以期望该应用程序在10.0.0.2:3100110.0.0.3:31001端点可用。请注意,对于负载均衡器服务,如果您的基础设施没有负载均衡器,您可以考虑使用keepalivedgithub.com/munnerz/keepalived-cloud-provider)。

  1. 打开 PowerShell 窗口,并使用kubectl应用清单文件,就像这样:
kubectl apply -f .\windows-example.yaml
  1. 等待 Pod 启动——初始镜像拉取可能需要几分钟。您可以使用以下命令观察 Pod 的状态:
PS C:\src> kubectl get pods --watch 
NAME                               READY STATUS   RESTARTS  AGE
windows-example-66cdf8c4bf-4472x   1/1   Running   0        9m17s
windows-example-66cdf8c4bf-647x8   1/1   Running   0        9m17s
windows-example-66cdf8c4bf-zxjdv   1/1   Running   0        9m17s
  1. 打开你的互联网浏览器,转到http://10.0.0.2:31001http://10.0.0.3:31001。您应该看到确认部署成功的示例应用程序网页,如下截图所示:

现在,让我们执行两个在调试应用程序时有用的常见操作,如下所示:

  1. 检索 Pod 容器日志:要访问部署中一个 Pod(windows-example-66cdf8c4bf-4472x)的日志,请使用以下kubectl logs命令:
PS C:\src> kubectl logs windows-example-66cdf8c4bf-4472x
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
 Storing keys in a directory 'C:\Users\ContainerUser\AppData\Local\ASP.NET\DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
info: Microsoft.Hosting.Lifetime[0]
 Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
 Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
 Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
 Content root path: C:\app
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
 Failed to determine the https port for redirect.
  1. 执行进入 Pod 容器以检查应用程序配置。要启动一个新的cmd shell(nanoserver镜像中不可用 PowerShell),请运行以下kubectl exec命令:
PS C:\src> kubectl exec -it windows-example-66cdf8c4bf-4472x cmd
Microsoft Windows [Version 10.0.17763.802]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\app>
  1. 您现在可以自由访问和修改容器,这在调试和测试场景中非常有用。例如,您可以像这样获取appsettings.json文件的内容:
C:\app>type appsettings.json
{
 "Logging": {
 "LogLevel": {
 "Default": "Information",
 "Microsoft": "Warning",
 "Microsoft.Hosting.Lifetime": "Information"
 }
 },
 "AllowedHosts": "*"
}

正如您所看到的,将示例 Windows 应用程序部署到 Kubernetes 集群非常容易,而且您从 Linux Kubernetes 集群中了解的所有调试技术都完全相同。

摘要

在本章中,您已经学习了如何在 Hyper-V 主机上创建本地 Windows/Linux Kubernetes 集群的过程。这种方法对于创建本地开发集群以及在云环境之外部署生产集群非常有用。让我们回顾一下程序——我们首先规划了节点、Pod 和 Service 的集群设计和网络无类域间路由CIDRs)。然后,我们创建了 Ubuntu Server 18.04 LTS VM——我们的 Kubernetes 主节点。创建主节点需要对操作系统进行初始配置并安装 Docker。使用 kubeadm 进行初始化。下一个重要步骤是安装 Kubernetes Pod 网络,该网络必须与 Linux 和 Windows 节点兼容。在我们的情况下,我们选择了带有host-gw后端的 Flannel,这是目前唯一稳定的本地混合集群的网络解决方案。之后,您将学习如何创建 Windows Server 2019 LTS VM 以及如何使用 kubeadm 和sig-windows-tools脚本将机器加入 Kubernetes 集群。最后,我们部署了一个示例 ASP.NET Core 3.0 应用程序,并执行了常见操作,如访问容器日志或进入容器。

在下一章中,您将学习如何使用 AKS Engine 执行类似的集群部署。目前,这是在云中部署混合 Kubernetes 集群的最佳和最稳定的方法。

问题

  1. 何时应该使用内部 NAT Hyper-V vSwitch?外部 vSwitch 有哪些用例?

  2. 为准备 Linux 节点或主节点需要哪些配置步骤?

  3. 服务子网范围和 Pod 子网范围是什么?

  4. 如何生成一个新的 kubeadm 令牌以加入集群?

  5. 如何允许将应用程序 Pod 调度到主节点?

  6. 在本地集群中,Linux 和 Windows 节点的推荐网络解决方案是什么?

  7. 加入集群的 Windows 节点需要执行哪些步骤?

  8. 访问 Pod 容器日志的命令是什么?

您可以在本书的后部的评估中找到这些问题的答案。

进一步阅读

这两个指南经常更新,因此值得检查它们,因为随着时间的推移,部署过程可能会变得更加简化。

第八章:部署混合 Azure Kubernetes 服务引擎集群

上一章概述了如何在本地环境中创建混合 Windows/Linux Kubernetes 集群的方法。这种方法也可以用于基础设施即服务云环境中的部署,但如果您使用 Azure,您有一个更简单的解决方案:Azure Kubernetes 服务AKS引擎 (github.com/Azure/aks-engine)。该项目旨在提供一种使用Azure 资源管理器ARM)模板部署自管理 Kubernetes 集群的 Azure 本地方式,可以利用 Kubernetes 的所有 Azure 云集成,例如负载均衡器服务。此外,使用 AKS Engine,您可以支持使用 Windows 节点部署 Kubernetes 集群,与本地环境相比,需要的配置和节点准备较少。换句话说,您将能够在几分钟内部署一个生产级的高可用混合集群。

AKS Engine 与其他概念(如 AKS、acs-engine 和 Azure Stack)的关系进行简要总结非常重要:

  • AKS Engine 和 AKS 不是相同的 Azure 产品。AKS 是一个 Azure 服务,可以让您创建一个完全托管的 Kubernetes 集群-我们在第四章中概述了 AKS 并演示了如何使用 AKS 部署混合 Windows/Linux 集群,但 AKS Engine 是 AKS 内部使用的,但您不能使用 AKS 来管理 AKS

  • acs-engine 是 AKS Engine 的前身,因此您可能会发现很多文档提到 acs-engine 而不是 AKS Engine。AKS Engine 是 acs-engine 的向后兼容的延续。

  • 从技术上讲,如果您使用 Azure Stack,也可以在本地环境中使用 AKS Engine。您可以在这里阅读更多信息:docs.microsoft.com/en-us/azure-stack/user/azure-stack-kubernetes-aks-engine-overview

在本章中,我们将重点关注 AKS Engine 并在 Azure 云中部署混合 Windows/Linux 集群。我们将涵盖以下主题:

  • 安装 AKS Engine

  • 创建 Azure 资源组和服务主体

  • 使用 API 模型和生成 Azure 资源管理器模板

  • 部署集群

  • 部署和检查您的第一个应用程序

技术要求

对于本章,您将需要以下内容:

  • 已安装 Windows 10 Pro、企业版或教育版(1903 版本或更高版本,64 位)

  • 一个 Azure 账户

  • 已安装 Windows 的 Chocolatey 软件包管理器(chocolatey.org/

  • 可选地,如果您想要可视化 AKS Engine 生成的 ARM 模板,可以安装 Visual Studio Code

使用 Chocolatey 软件包管理器并非强制,但它可以使安装过程和应用程序版本管理更加简单。安装过程在此处有文档:chocolatey.org/install

要跟着做,您需要自己的 Azure 账户以创建 Kubernetes 集群的 Azure 资源。如果您之前还没有创建过账户,您可以在此处了解如何获取个人使用的有限免费账户:azure.microsoft.com/en-us/free/

您可以从官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter08

安装 AKS Engine

AKS Engine 本身是一个命令行工具,可以根据提供的配置文件生成基于 Azure 资源管理器(ARM)模板。要使用 AKS Engine,您需要以下内容,其安装过程在前几章中有描述:

  • Azure CLI 和 Azure Cloud Shell: 指南可在第二章中找到,管理容器中的状态,在使用远程/云存储进行容器存储部分。

  • kubectl: 指南可在第六章中找到,与 Kubernetes 集群交互,在安装 Kubernetes 命令行工具部分。

  • Windows 下的 SSH 客户端: 指南可在第七章中找到,部署混合本地 Kubernetes 集群,在使用 kubeadm 创建 Kubernetes 主节点部分。

在您的计算机上安装了所有工具后,您可以继续安装 AKS Engine 本身。在 Windows 上推荐的安装方法是使用 Chocolatey。或者,您可以下载 AKS Engine 二进制文件(github.com/Azure/aks-engine/releases/latest),解压它们,并将它们添加到您的$env:PATH环境变量中。要使用 Chocolatey 安装 AKS Engine,请按照以下步骤进行:

  1. 以管理员身份打开 PowerShell 窗口。

  2. 要安装aks-engine软件包,请执行以下命令:

choco install aks-engine
  1. 如果您想安装特定版本的 AKS Engine,例如0.42.0,请使用以下命令:
choco install aks-engine --version=0.42.0
  1. 验证您的安装是否成功:
PS C:\src> aks-engine version
Version: v0.42.0
GitCommit: 0959ab812
GitTreeState: clean

现在,您已经准备好继续下一步-配置 Kubernetes 集群的先决条件。让我们开始收集初始集群信息并创建 Azure 资源组。

创建 Azure 资源组和服务主体

在使用 AKS Engine 部署 Kubernetes 集群之前,我们需要执行以下初始步骤:

  1. 您需要确保在 Azure 订阅中具有适当的权限来创建和分配 Azure 活动目录服务主体。如果您只是为了进行演示而创建了 Azure 帐户,则默认情况下将具有权限。

  2. 确定要用于部署集群的 Azure 订阅的SubscriptionId。您可以通过打开 PowerShell 窗口并执行以下命令来执行此操作:

PS C:\src> az login
PS C:\src> az account list -o table
Name           CloudName    SubscriptionId                        State    IsDefault
-------------  -----------  ------------------------------------  -------  -----------
Pay-As-You-Go  AzureCloud   cc9a8166-829e-401e-a004-76d1e3733b8e  Enabled  True

在接下来的段落中,我们将使用cc9a8166-829e-401e-a004-76d1e3733b8e作为SubscriptionId

  1. 确定一个全局唯一的dnsPrefix,您想要用于集群内主机名的。或者,您可以依赖于 AKS Engine 自动生成的前缀。在接下来的段落中,我们将使用handson-aks-engine-win作为前缀。

  2. 选择要用于部署集群的 Azure 位置。在接下来的示例中,我们将使用westeurope

  3. 为您的集群选择一个新的 Azure 资源组的名称。在接下来的段落中,我们将使用aks-engine-windows-resource-group

  4. 为 Windows 节点选择用户名和密码。为此,我们将使用azureuserS3cur3P@ssw0rd-请记住使用您自己的安全密码!

  5. 生成一个 SSH 密钥对,您可以用它来连接到 Linux 节点。如果您选择在 Windows 节点上安装 OpenSSH 服务器,您以后可以使用相同的密钥对来访问 Windows 节点。

下一段描述的先决条件创建和 AKS Engine 部署步骤已在此处提供的 PowerShell 脚本中捕获:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter08/01_aks-engine/01_CreateAKSEngineClusterWithWindowsNodes.ps1

现在,请按照以下步骤创建 Azure 资源组和 Azure 活动目录服务主体:

  1. 打开 PowerShell 窗口并使用 Azure CLI 登录:
az login
  1. 使用以下命令为您的集群创建 Azure 资源组:
az group create `
 --name aks-engine-windows-resource-group `
 --location westeurope
  1. 为您的集群创建 Azure 活动目录服务主体。使用适当的Subscription IDResource Group名称:
az ad sp create-for-rbac `
 --role="Contributor" `
 --scopes="/subscriptions/cc9a8166-829e-401e-a004-76d1e3733b8e/resourceGroups/aks-engine-windows-resource-group"

请注意,如果范围仅限于特定的资源组,则将无法使用容器监视附加组件。我们将在接下来的章节中介绍为此目的配置 AAD 服务主体。

  1. 检查上一个命令的输出,并注意appIdpassword。您无法以后检索密码:
{
 "appId": "7614823f-aca5-4a31-b2a5-56f30fa8bd8e",
 "displayName": "azure-cli-2019-10-19-12-48-08",
 "name": "http://azure-cli-2019-10-19-12-48-08",
 "password": "8737c1e6-b1b1-4c49-a195-f7ea0fe37613",
 "tenant": "86be0945-a0f3-44c2-8868-9b6aa96b0b62"
}

最后一步是为访问集群中的 Linux 节点生成 SSH 密钥对:

  1. 打开 PowerShell 窗口。

  2. 如果您已经按照前面的章节操作,可能已经生成了一个 SSH 密钥对,您可以重复使用并跳过下一步。要检查是否有现有的 SSH 密钥对,请使用以下命令:

ls ~\.ssh\id_rsa.pub
  1. 如果您需要生成密钥对,请执行以下命令(建议使用默认值):
ssh-keygen

现在,您拥有 AKS Engine 部署所需的所有信息。我们需要做的就是准备 AKS Engine apimodel 并为我们的集群生成 ARM 模板。

使用 apimodel 并生成 Azure 资源管理器模板

在其核心,AKS Engine 使用一个apimodel(或集群定义)JSON 文件来生成可以用于直接部署 Kubernetes 集群到 Azure 的 Azure 资源管理器模板。apimodel 的文档和模式可以在这里找到:github.com/Azure/aks-engine/blob/master/docs/topics/clusterdefinitions.md。AKS Engine 在集群定义中提供了对 Windows 节点的开箱即用支持。您可以在官方 AKS Engine GitHub 存储库中找到示例:github.com/Azure/aks-engine/tree/master/examples/windows

现在让我们根据最小的 Windows 集群示例定义(github.com/Azure/aks-engine/blob/master/examples/windows/kubernetes.json)创建一个自定义的 apimodel。我们还将包括两个 Linux 节点,以便运行混合的 Windows/Linux 配置(一个 Linux 主节点,两个 Windows 节点和两个 Linux 节点)。按照以下步骤进行:

  1. 下载以下文件,并将其保存为kubernetes-windows.jsonraw.githubusercontent.com/Azure/aks-engine/master/examples/windows/kubernetes.json

  2. properties.orchestratorProfile.orchestratorRelease更改为所需的 Kubernetes 版本,例如1.16

  3. properties.masterProfile.dnsPrefix修改为所选的 DNS 前缀。在示例中,我们使用handson-aks-engine-win,但您需要选择您自己的唯一前缀。

  4. 通过将以下 JSON 对象添加到properties.agentPoolProfiles中,添加一个 Linux 节点池:

{
    "name": "linuxpool1",
    "count": 2,
    "vmSize": "Standard_D2_v3",
    "availabilityProfile": "AvailabilitySet"
}
  1. properties.windowsProfile.adminUsernameproperties.windowsProfile.adminPassword修改为 Windows 节点的所选用户名和密码。

  2. ~\.ssh\id_rsa.pub的内容复制到properties.linuxProfile.ssh.publicKeys.keyData

  3. properties.servicePrincipalProfile.clientId中使用服务主体appId,在properties.servicePrincipalProfile.secret中使用password

  4. 一个示例的自定义文件具有以下内容:

{
 "apiVersion": "vlabs",
    "properties": {
        "orchestratorProfile": {
            "orchestratorType": "Kubernetes",
            "orchestratorRelease": "1.16"
        },
        "masterProfile": {
            "count": 1,
            "dnsPrefix": "handson-aks-engine-win",
            "vmSize": "Standard_D2_v3"
        },
        "agentPoolProfiles": [{
                "name": "linuxpool1",
                "count": 2,
                "vmSize": "Standard_D2_v3",
                "availabilityProfile": "AvailabilitySet"
            },{
                "name": "windowspool2",
                "count": 2,
                "vmSize": "Standard_D2_v3",
                "availabilityProfile": "AvailabilitySet",
                "osType": "Windows",
                "osDiskSizeGB": 128,
                "extensions": [{
                        "name": "winrm"
                    }
                ]
            }
        ],
        "windowsProfile": {
            "adminUsername": "azureuser",
            "adminPassword": "S3cur3P@ssw0rd",
            "sshEnabled": true
        },
        "linuxProfile": {
            "adminUsername": "azureuser",
            "ssh": {
                "publicKeys": [{
                        "keyData": "<contents of ~\.ssh\id_rsa.pub>"
                    }
                ]
            }
        },
        "servicePrincipalProfile": {
            "clientId": "8d4d1104-7818-4883-88d2-2146b658e4b2",
            "secret": "9863e38c-896f-4dba-ac56-7a3c1849a87a"
        },
        "extensionProfiles": [{
                "name": "winrm",
                "version": "v1"
            }
        ]
    }
}

apimodel 已准备好供 AKS Engine 使用。使用以下命令生成 ARM 模板:

aks-engine generate .\kubernetes-windows.json

这将在_output\<dnsPrefix>目录中生成 ARM 模板(带参数)、完整的 apimodel 和 kubeconfigs(对于每个可能的 Azure 位置)。您可以检查这些文件以了解集群的设计方式 - 可选地,如果您安装了 Visual Studio Code,可以使用以下优秀的扩展来可视化 ARM 模板 - marketplace.visualstudio.com/items?itemName=bencoleman.armview

  1. 在 VS Code 中,安装了扩展程序后,打开_output\<dnsPrefix>\azuredeploy.json ARM 模板文件。

  2. 使用以下图标可视化 ARM 模板:

  1. 使用以下图标加载 ARM 模板参数_output\<dnsPrefix>\azuredeploy.parameters.json

  1. 您现在可以方便地检查 ARM 模板:

此时,我们准备使用 Azure CLI 部署集群!

部署集群

为了从 Azure 资源管理器模板部署 Kubernetes 集群,我们将使用az group deployment create命令。这个 Azure CLI 命令需要传递 ARM 模板(_output\<dnsPrefix>\azuredeploy.json)和 ARM 参数文件(_output\<dnsPrefix>\azuredeploy.parameters.json)。要部署集群,请执行以下步骤:

  1. 执行命令:
az group deployment create `
 --name kubernetes-windows-cluster `
 --resource-group aks-engine-windows-resource-group `
 --template-file ".\_output\<dnsPrefix>\azuredeploy.json" `
 --parameters ".\_output\<dnsPrefix>\azuredeploy.parameters.json"

如果遇到任何问题,您可以通过转到资源组并单击右上角的“部署:失败”链接,在 Azure 门户中检查 ARM 部署失败的详细信息。对于任何InternalServerErrors,您可以尝试选择另一个 Azure 位置,例如westus

  1. 部署完成后,将以 JSON 格式返回创建的资源列表和输出变量:
      ...
      "agentStorageAccountSuffix": {
        "type": "String",
        "value": ""
      },
      "masterFQDN": {
        "type": "String",
        "value": "<dnsPrefix>.westeurope.cloudapp.azure.com"
      },
      "primaryAvailabilitySetName": {
        "type": "String",
        "value": "windowspool2-availabilitySet-70017404"
      },
      ...
  1. 提取masterFQDN属性。这是您的 Kubernetes 主 DNS 名称。

或者,您可以采用使用aks-engine deploy命令的方法,该命令结合了自定义集群定义和生成并部署 ARM 模板为一条命令。请注意,为了使用容器监视功能,您现在必须使用aks-engine deploy命令。

现在,我们需要连接到我们的新集群。 AKS Engine 与 ARM 模板一起生成了一组kubeconfigs,用于所有可能的 Azure 位置,格式为.\_output\<dnsPrefix>\kubeconfig\kubeconfig.<azureLocation>.json。在我们的情况下,我们使用了位置westeurope,因此 kubeconfig 是.\_output\<dnsPrefix>\kubeconfig\kubeconfig.westeurope.json。要将此 kubeconfig 用于您的 kubectl,您可以使用第六章中提供的配置管理技术之一,与 Kubernetes 集群交互。例如,要将此文件与您现有的默认 kubeconfig 合并,请执行以下步骤:

  1. 检查.\_output\<dnsPrefix>\kubeconfig\kubeconfig.westeurope.json的内容,以了解集群名称和上下文名称。两者应与您的<dnsPrefix>相同。

  2. 执行文件与默认 kubeconfig 的测试合并。使用以下命令:

$env:KUBECONFIG=".\_output\<dnsPrefix>\kubeconfig\kubeconfig.westeurope.json;$env:USERPROFILE\.kube\config"
kubectl config view --raw
  1. 仔细检查输出,以确定合并的配置是否包含您期望的所有集群和上下文。

  2. 将合并后的文件保存为默认配置,并切换到使用新的<dnsPrefix>上下文:

$env:KUBECONFIG=".\_output\<dnsPrefix>\kubeconfig\kubeconfig.westeurope.json;$env:USERPROFILE\.kube\config"
kubectl config view --raw > $env:USERPROFILE\.kube\config_new 
Move-Item -Force $env:USERPROFILE\.kube\config_new $env:USERPROFILE\.kube\config

kubectl config use-context "<dnsPrefix>"
  1. 测试与您的新集群的连接:
PS C:\src\temp> kubectl get nodes --all-namespaces
NAME                        STATUS   ROLES    AGE   VERSION
7001k8s000                  Ready    agent    16m   v1.16.1
7001k8s001                  Ready    agent    16m   v1.16.1
k8s-linuxpool1-70017404-0   Ready    agent    13m   v1.16.1
k8s-linuxpool1-70017404-1   Ready    agent    13m   v1.16.1
k8s-master-70017404-0       Ready    master   18m   v1.16.1
PS C:\src\temp> kubectl get pods --all-namespaces
NAMESPACE     NAME                                            READY   STATUS    RESTARTS   AGE
kube-system   azure-cni-networkmonitor-ftnqs                  1/1     Running   0          18m
kube-system   azure-ip-masq-agent-vqdhz                       1/1     Running   0          18m
...

请注意,由 AKS Engine 部署的集群根据在 ARM 模板中使用的资源计费。您应该使用 Azure 定价计算器来确定预计成本。如果您不再需要该集群,建议删除以避免任何不必要的费用。要删除 AKS Engine 集群,请使用以下命令:az group delete --name aks-engine-windows-resource-group --yes --no-wait

恭喜!您已经使用 AKS Engine 在 Azure 上部署了一个功能齐全的混合 Windows/Linux 集群!让我们看看如何将一个简单的应用程序部署到集群,并与集群进行交互。

部署和检查您的第一个应用程序

在这一部分,我们将执行与上一章类似的练习 - 我们将部署一个示例的 ASP.NET Core 3.0 应用程序(使用部署对象)到我们的 AKS Engine 集群,并演示基本的 kubectl 操作。与在本地集群的工作许多方面保持一致 - 最大的区别是您可以轻松地利用 Azure 的功能和集成。为了演示这一点,我们将使用 LoadBalancer 类型的服务来暴露应用程序,而不是 NodePort。由于 Kubernetes 云提供程序为 Azure,LoadBalancer 服务将与 Azure 负载均衡器实例进行本地集成。

基本操作

要部署示例应用程序,请按照以下步骤进行:

  1. 创建一个windows-example.yaml清单文件,其中包含一个部署和服务定义。您可以从 GitHub 存储库(raw.githubusercontent.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/master/Chapter08/03_windows-example/windows-example.yaml)下载它,或者直接将其应用到集群中:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: windows-example
  labels:
    app: sample
spec:
  replicas: 3
  selector:
    matchLabels:
      app: windows-example
  template:
    metadata:
      name: windows-example
      labels:
        app: windows-example
    spec:
      nodeSelector:
 "beta.kubernetes.io/os": windows
      containers:
      - name: windows-example
        image: mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809
        resources:
          limits:
            cpu: 1
            memory: 800M
          requests:
            cpu: .1
            memory: 300M
        ports:
          - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: windows-example
spec:
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
  selector:
    app: windows-example

在这个清单文件中有三个重要的点,已经用粗体标记出来:

    • 为 Windows 节点调度需要使用nodeSelector,其值为"beta.kubernetes.io/os": windows。同样,如果您需要为 Linux 节点调度 Pods,您应该在混合集群中使用"beta.kubernetes.io/os": linux节点选择器。这与本地集群完全相同。
  • Pod 定义包括一个基于mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809镜像的容器。确保容器的主机操作系统版本与容器的基本镜像版本兼容非常重要-这个要求与本地集群中的要求相同。您可以使用 AKS Engine apimodel JSON 文件中的properties.windowsProfile中的自定义windowsSku属性来控制 AKS Engine 集群中的 Windows Server 版本。您可以在官方文档中阅读更多内容:github.com/Azure/aks-engine/blob/master/docs/topics/windows-and-kubernetes.md#choosing-the-windows-server-version。对于现有集群,您可以使用kubectl get nodes -o wide命令检查节点的 Windows Server 版本。

  • 服务规范的类型设置为LoadBalancer。这将导致为服务创建一个外部可访问的 Azure 负载均衡器。您可以在第五章中了解更多关于这种类型服务的信息,Kubernetes Networking

  1. 打开 PowerShell 窗口并使用kubectl应用清单文件。如果您还没有合并您的kubeconfigs,请记得首先设置正确的$env:KUBECONFIG变量,并切换到适当的 kubectl 上下文:
kubectl apply -f .\windows-example.yaml
  1. 等待 Pod 启动-初始镜像拉取可能需要几分钟。您可以使用以下命令观察 Pod 的状态:
PS C:\src> kubectl get pods --watch 
NAME                               READY   STATUS    RESTARTS   AGE
windows-example-66cdf8c4bf-f5bd8   1/1     Running   0          101s
windows-example-66cdf8c4bf-g4v4s   1/1     Running   0          101s
windows-example-66cdf8c4bf-xkbpf   1/1     Running   0          101s
  1. 等待服务的外部 IP 创建。您可以使用以下命令观察服务的状态:
PS C:\src> kubectl get services --watch
NAME              TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
kubernetes        ClusterIP      10.0.0.1       <none>           443/TCP        24m
windows-example   LoadBalancer   10.0.158.121   52.136.234.203   80:32478/TCP   3m55s
  1. 打开您的互联网浏览器并导航到 Azure 负载均衡器地址-在这个例子中,它是http://52.136.234.203/。您应该看到示例应用程序的网页,这证实了部署成功:

执行常见操作,如访问 Pod 容器日志或在 Pod 容器内执行临时进程,与本地集群完全相同-我们将简要回顾如何做到这一点:

  1. 为了访问作为部署的一部分创建的一个 Pod(windows-example-66cdf8c4bf-f5bd8)的日志,使用以下kubectl logs命令:
kubectl logs windows-example-66cdf8c4bf-f5bd8
  1. exec到相同的 Pod 容器中,例如,启动一个交互式cmd shell 并运行kubectl exec命令:
kubectl exec -it windows-example-66cdf8c4bf-f5bd8 cmd
  1. 您现在可以自由访问和修改容器,这在调试和测试场景中非常有用。例如,您可以获取appsettings.json文件的内容:
C:\app>type appsettings.json
{
 "Logging": {
 "LogLevel": {
 "Default": "Information",
 "Microsoft": "Warning",
 "Microsoft.Hosting.Lifetime": "Information"
 }
 },
 "AllowedHosts": "*"
}

接下来,让我们看看如何连接到在 AKS Engine 集群中使用的实际虚拟机。

连接到虚拟机

要连接到 Linux 主虚拟机,您可以使用 SSH 并直接连接到它,因为它暴露在公共网络中:

  1. 在 PowerShell 窗口中,执行以下命令(您的公共 SSH 密钥将用于身份验证):
ssh azureuser@<dnsPrefix>.westeurope.cloudapp.azure.com
  1. 现在,您可以执行任何维护或调试操作,例如访问 kubelet 服务日志:
azureuser@k8s-master-70017404-0:~$ sudo journalctl -u kubelet -o cat
Stopped Kubelet.
Starting Kubelet...
net.ipv4.tcp_retries2 = 8
Bridge table: nat
Bridge chain: PREROUTING, entries: 0, policy: ACCEPT
Bridge chain: OUTPUT, entries: 0, policy: ACCEPT
Bridge chain: POSTROUTING, entries: 0, policy: ACCEPT
Chain PREROUTING (policy ACCEPT)

对于 Windows 节点(或其他 Linux 节点),该过程会更加复杂,因为 VM 位于私有 IP 范围内。这意味着您需要通过 Linux 主节点使用 SSH 本地端口转发来连接远程桌面连接或 SSH:

  1. 首先,查询要连接的 Windows 节点的私有 IP 地址。您可以使用以下命令查看所有节点的名称:
az vm list --resource-group aks-engine-windows-resource-group -o table
  1. 使用名称获取节点的私有 IP 地址,例如7001k8s000
PS C:\src> az vm show -g aks-engine-windows-resource-group -n 7001k8s000 --show-details --query 'privateIps'
"10.240.0.4,10.240.0.5,10.240.0.6,10.240.0.7,10.240.0.8,10.240.0.9,10.240.0.10,10.240.0.11,10.240.0.12,10.240.0.13,10.240.0.14,10.240.0.15,10.240.0.16,10.240.0.17,10.240.0.18,10.240.0.19,10.240.0.20,10.240.0.21,10.240.0.22,10.240.0.23,10.240.0.24,10.240.0.25,10.240.0.26,10.240.0.27,10.240.0.28,10.240.0.29,10.240.0.30,10.240.0.31,10.240.0.32,10.240.0.33,10.240.0.34"
  1. 使用其中一个私有 IP 来创建一个 SSH 隧道,从您的本地5500端口通过主节点到3389端口(RDP)连接到 Windows 节点:
ssh -L 5500:10.240.0.4:3389 azureuser@<dnsPrefix>.westeurope.cloudapp.azure.com
  1. 在另一个 PowerShell 窗口中,通过隧道启动一个 RDP 会话:
mstsc /v:localhost:5500
  1. 提供您的 Windows 节点凭据(如在 apimodel 中)并连接:

  1. 或者,您也可以从主节点使用 SSH:
ssh 10.240.0.4

现在,让我们看看如何为容器启用 Azure Log Analytics 和 Azure Monitor。

启用 Azure Log Analytics 和 Azure Monitor for containers

AKS Engine 集成了 Azure Log Analytics 和 Azure Monitor for containers,这是由运行在集群节点上的运营管理套件OMS)代理提供的。在部署 Kubernetes 集群时,您可以在 Kubernetes 集群定义中指定一个额外的container-monitoring附加组件 - 请注意,目前,您必须在创建新集群时启用容器监控;您不能修改现有的集群定义。

此外,此功能仅在使用aks-engine deploy命令时才能使用。如果您想使用此功能,请执行以下步骤:

  1. 如果您在aks-engine-windows-resource-group中有现有的集群,请先删除它。

  2. 修改您的集群定义(apimodel),使properties.orchestratorProfile.kubernetesConfig属性具有以下内容。或者,您可以使用github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter08/05_windows-apimodel-container-monitoring/kubernetes-windows.json作为基础:

{
    "addons": [{
            "name": "container-monitoring",
            "enabled": true
        }
    ]
}
  1. 确保您的服务主体(在本例中为appId: 7614823f-aca5-4a31-b2a5-56f30fa8bd8e)还具有 Azure 订阅的Log Analytics Contributor角色:
az role assignment create `
 --assignee 7614823f-aca5-4a31-b2a5-56f30fa8bd8e `
 --role "Log Analytics Contributor" `
 --scope="/subscriptions/cc9a8166-829e-401e-a004-76d1e3733b8e"
  1. 执行 AKS Engine 部署,使用服务主体appId作为--client-idpassword作为--client-secret
aks-engine deploy `
 --subscription-id cc9a8166-829e-401e-a004-76d1e3733b8e `
 --resource-group aks-engine-windows-resource-group `
 --location westeurope `
 --api-model .\kubernetes-windows.json `
 --client-id 7614823f-aca5-4a31-b2a5-56f30fa8bd8e `
 --client-secret 8737c1e6-b1b1-4c49-a195-f7ea0fe37613 `
 --force-overwrite
  1. 几分钟后,您的集群将准备就绪,您可以将默认 kubeconfig 与 AKS Engine kubeconfig 合并。

这个container-monitoring附加组件将使两件事情成为可能:

  1. 使用 Azure Log Analytics 来使用 Kusto 查询语言查询 Kubernetes 和您的应用程序日志(docs.microsoft.com/en-us/azure/azure-monitor/log-query/get-started-portal

  2. 使用 Azure Monitor 服务来监视在您的集群中运行的容器(docs.microsoft.com/en-us/azure/azure-monitor/insights/container-insights-overview

请注意,在 AKS Engine 中的github.com/Azure/aks-engine/issues/2066问题得到解决之前,Kubernetes 1.16 将无法正确集成 Log Analytics 和 Monitor 服务。您可以尝试使用不同的 Kubernetes 版本在 apimodel 中重新部署您的集群。

这些服务为在 Kubernetes 上运行的容器化应用程序提供了监视、警报和调试的基本构建模块-您可以利用多个 Azure 门户 UI 功能来使分析和管理更加容易,例如:

  1. 使用以下 URL 访问 Azure Monitor for containers:aka.ms/azmon-containers。Azure Monitor 值得拥有一本单独的书籍来覆盖其所有功能-例如,您可以探索为监视您的 Kubernetes 集群提供的默认仪表板:

  1. 使用以下 URL 访问 Azure 日志分析:portal.azure.com/#blade/Microsoft_Azure_Monitoring/AzureMonitoringBrowseBlade/logs。展开 ContainerInsights 数据库,并选择,例如,KubeEvents 表。您现在可以执行一个简单的 Kusto 查询来检查表中的数据:
KubeEvents
| limit 50

以下屏幕截图显示了前面命令的输出:

您可以在官方文档中了解有关容器监视的 Log Analytics 表的更多信息:docs.microsoft.com/en-us/azure/azure-monitor/insights/containers#monitor-containers。有关 Kusto 查询语言的文档,请参阅docs.microsoft.com/en-us/azure/azure-monitor/log-query/query-language。建立适当的监视和日志分析解决方案对于运行分布式应用程序至关重要。如果您没有在 Azure 上使用集群,您可以考虑不同的解决方案,例如 Prometheus 和 Elasticsearch,它们提供类似的功能。

摘要

在本章中,您已经学会了如何使用 AKS Engine 在 Azure 上部署运行的混合 Windows/Linux Kubernetes 集群。目前,这种方法是运行具有 Windows 节点的自管理生产级集群的最合适解决方案。部署过程很简单-首先,在本地机器上安装所有先决条件和 AKS Engine,然后创建一个专用的 Azure 资源组和 Azure 活动目录服务主体。之后,您需要为 AKS Engine 定义一个 apimodel(集群定义)JSON 文件,以生成 Azure 资源管理器模板,并使用该模板部署集群。此外,您已经学会了如何部署一个示例 Windows 容器应用程序,以及如何使用 kubectl 执行基本操作。除此之外,我们还展示了如何连接到集群中的虚拟机进行调试和监视,并如何为您的集群启用 Azure Monitor 和 Azure 日志分析。

下一章将进一步关注将应用程序部署到 Kubernetes 集群以及如何在集群中执行“第二天”的操作。

问题

  1. AKS 和 AKS Engine 之间有什么区别?

  2. AKS Engine 的基本工作原理是什么?

  3. 您能使用 AKS Engine 管理 AKS 集群,反之亦然吗?

  4. 使用 AKS Engine 的先决条件是什么?

  5. AKS Engine 中的 apimodel 是什么?

  6. 如何连接到 Kubernetes Linux 主节点?

  7. 如何连接到 Kubernetes Windows 节点?

您可以在本书的评估中找到这些问题的答案。

进一步阅读

第四部分:使用 Kubernetes 编排 Windows 容器

在本节中,我们将使用 Kubernetes 来创建简单和复杂的应用程序架构,使用容器。我们将学习如何部署、扩展和监控这些应用程序,利用平台的强大功能。

本节包括以下章节:

  • 第九章,部署您的第一个应用程序

  • 第十章,部署 Microsoft SQL Server 2019 和 ASP.NET MVC 应用程序

  • 第十一章,配置应用程序以使用 Kubernetes 功能

  • 第十二章,使用 Kubernetes 进行开发工作流

  • 第十三章,保护 Kubernetes 集群和应用程序

  • 第十四章,使用 Prometheus 监控 Kubernetes 应用程序

  • 第十五章,灾难恢复

  • 第十六章,运行 Kubernetes 的生产考虑

第九章:部署您的第一个应用程序

在前几章中,我们介绍了 Kubernetes 的关键操作原则和 Windows/Linux 混合集群的部署策略。现在是时候更专注于部署和使用 Kubernetes 应用程序了。为了演示 Kubernetes 应用程序的基本操作,我们将使用在第八章中创建的 AKS Engine 混合 Kubernetes 集群,部署混合 Azure Kubernetes 服务引擎集群。您也可以使用本地混合集群,但您应该期望功能有限;例如,LoadBalancer 类型的服务将不可用。

本章涵盖以下主题:

  • 命令式部署应用程序

  • 使用 Kubernetes 清单文件

  • 在 Windows 节点上调度 Pods

  • 访问您的应用程序

  • 扩展应用程序

技术要求

本章,您将需要以下内容:

  • 已安装 Windows 10 Pro、企业版或教育版(1903 版本或更高版本,64 位)

  • Azure 帐户

  • 使用 AKS Engine 部署的 Windows/Linux Kubernetes 集群

要跟着做,您需要自己的 Azure 帐户来为 Kubernetes 集群创建 Azure 资源。如果您之前还没有为前几章创建帐户,您可以在这里阅读更多关于如何获得个人使用的有限免费帐户:azure.microsoft.com/en-us/free/

使用 AKS Engine 部署 Kubernetes 集群已在第八章中介绍过,部署混合 Azure Kubernetes 服务引擎集群

您可以从官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter09

命令式部署应用程序

在 Kubernetes 世界中,管理应用程序时可以选择两种方法:命令式管理和声明式管理。命令式方法包括执行命令式 kubectl 命令,例如kubectl runkubectl expose,以及命令式对象配置管理,其中您使用命令,如kubectl createkubectl replace。简而言之,您通过执行临时命令来管理集群,这些命令修改 Kubernetes 对象并导致集群的期望状态发生变化 - 有时,您甚至可能不知道命令式命令之后期望状态的确切变化。相比之下,在声明式方法中,您修改对象配置(清单文件),并使用kubectl apply命令在集群中创建或更新它们(或者,您可以使用 Kustomization 文件)。

使用声明性管理通常更接近 Kubernetes 的精神 - 整个架构都专注于保持期望的集群状态,并不断执行操作,将当前集群状态更改为期望的状态。一个经验法则是,在生产环境中,您应该始终使用声明性管理,无论是使用标准清单文件还是 Kustomization 文件。您可以轻松为对象配置提供源代码控制,并将其集成到持续集成/部署流水线中。命令式管理对于开发和概念验证场景非常有用 - 操作直接在活动集群上执行。

请记住,对于这种方法,您将无法轻松地了解先前配置的历史!

现在,让我们首先尝试使用命令式方法部署一个简单的 Web 应用程序。我们将执行以下操作:

  1. 创建一个单独的裸 Pod 或 ReplicationController。

  2. 使用 Service(LoadBalancer 类型)来公开它。

要命令式地创建一个 pod 或 ReplicationController,我们将使用kubectl run命令。此命令允许您使用生成器创建不同的容器管理对象。您可以在官方文档中找到生成器的完整列表:kubernetes.io/docs/reference/kubectl/conventions/#generators——自 Kubernetes 1.12 以来,除了run-pod/v1之外的所有生成器都已被弃用。这样做的主要原因是kubectl run命令的相对复杂性,以及鼓励在高级场景中采用适当的声明性方法。

要部署基于mcr.microsoft.com/dotnet/core/samples:aspnetapp Docker 映像的示例应用程序,请执行以下步骤:

  1. 打开 PowerShell 窗口,并确保您正在使用kubeconfig文件,该文件允许您连接到您的 AKS Engine 混合集群。

  2. 确定集群中的节点上可用的 Windows Server 操作系统的版本。例如,对于 Windows Server 2019 Datacenter 节点,您需要使用具有基础层版本 1809 的容器映像。这意味着在我们的示例中,我们必须使用mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809 Docker 映像:

kubectl get nodes -o wide
  1. 使用run-pod/v1生成器执行kubectl run命令以运行单个 pod,windows-example,用于具有节点选择器和操作系统类型以及windows的示例应用程序:
kubectl run `
 --generator=run-pod/v1 `
 --image=mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809 `
 --overrides='{\"apiVersion\": \"v1\", \"spec\": {\"nodeSelector\": { \"beta.kubernetes.io/os\": \"windows\" }}}' `
 windows-example
  1. pod 将被调度到 Windows 节点之一,并且您可以使用以下命令监视 pod 创建的进度:
PS C:\src> kubectl get pods -w
NAME              READY   STATUS              RESTARTS   AGE
windows-example   0/1     ContainerCreating   0          7s
  1. 当 pod 将其状态更改为Running时,您可以继续使用 LoadBalancer 服务暴露 pod:
kubectl expose pod windows-example `
 --name windows-example-service `
 --type LoadBalancer `
 --port 8080 `
 --target-port 80
  1. 等待服务的EXTERNAL-IP可用:
PS C:\src> kubectl get service -w
NAME                      TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)          AGE
kubernetes                ClusterIP      10.0.0.1       <none>           443/TCP          24h
windows-example-service   LoadBalancer   10.0.192.180   213.199.135.14   8080:30746/TCP   5m10s
  1. 现在,您可以使用服务的外部 IP 和端口8080来访问在 pod 中运行的应用程序。例如,在 Web 浏览器中,导航到http://213.199.135.14:8080/

或者,前面的步骤可以在一个kubectl run命令中完成,该命令将创建 pod 并立即使用 LoadBalancer 服务进行暴露:

kubectl run `
 --generator=run-pod/v1 `
 --image=mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809 `
 --overrides='{\"apiVersion\": \"v1\", \"spec\": {\"nodeSelector\": { \"beta.kubernetes.io/os\": \"windows\" }}}' `
 --expose `
 --port 80 `
 --service-overrides='{ \"spec\": { \"type\": \"LoadBalancer\" }}' `
 windows-example

请注意,此命令使用端口80而不是8080来暴露服务。使用服务端口80和目标端口8080需要在--service-overrides标志中增加另一层复杂性。

为了完整起见,让我们在 Kubernetes ReplicationController 对象后面运行我们的mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809容器。您可以在第四章中了解有关 ReplicationControllers、ReplicaSets 和 Deployments 的更多信息,Kubernetes 概念和 Windows 支持——通常情况下,在集群中运行裸 Pods 并不明智;您应该始终使用至少 ReplicaSets 或更好地使用 Deployments 来管理 Pods。在 Kubernetes 1.17 中,仍然可以使用kubectl run创建 ReplicationController——生成器已被弃用,但尚未删除。使用命令来声明式地创建 ReplicationController 需要使用不同的--generator标志,其值为run/v1

kubectl run `
 --generator=run/v1  `
 --image=mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809 `
 --overrides='{\"apiVersion\": \"v1\", \"spec\": {\"nodeSelector\": { \"beta.kubernetes.io/os\": \"windows\" }}}' `
 --expose `
 --port 80 `
 --service-overrides='{ \"spec\": { \"type\": \"LoadBalancer\" }}' `
 windows-example

即使这种方法快捷且不需要任何配置文件,你可以清楚地看到,除了简单操作之外,使用kubectl run变得复杂且容易出错。在大多数情况下,您将使用命令来执行以下操作:

  • 在开发集群中快速创建 Pods

  • 为了调试目的创建临时交互式 Pods

  • 可预测地删除 Kubernetes 资源——在下一节中会详细介绍

现在让我们通过使用 Kubernetes 清单文件和声明式管理方法来执行类似的部署。

使用 Kubernetes 清单文件

Kubernetes 对象的声明式管理更接近于 Kubernetes 的精神——您专注于告诉 Kubernetes 您想要什么(描述所需状态),而不是直接告诉它要做什么。随着应用程序的增长和组件的增加,使用命令来管理集群变得不可能。最好使用命令来进行只读操作,例如kubectl describekubectl getkubectl logs,并使用kubectl apply命令和 Kubernetes 对象配置文件(也称为清单文件)对集群的期望状态进行所有修改。

在使用清单文件时有一些推荐的做法:

  • 最好使用 YAML 清单文件而不是 JSON 清单文件。 YAML 更容易管理,而且在 Kubernetes 社区中更常用。

  • 将您的清单文件存储在 Git 等源代码控制中。在将任何配置更改应用到集群之前,先将更改推送到源代码控制中——这将使回滚和配置恢复变得更加容易。最终,您应该将此过程自动化为 CI/CD 流水线的一部分。

  • 将多个清单文件组合成单个文件是推荐的,只要有意义。官方 Kubernetes 示例存储库提供了这种方法的很好演示:github.com/kubernetes/examples/blob/master/guestbook/all-in-one/guestbook-all-in-one.yaml.

  • 如果您的集群有多个清单文件,您可以使用 kubectl apply 递归地应用给定目录中的所有清单文件。

  • 使用 kubectl diff 来了解将应用到当前集群配置的变化。

  • 在删除 Kubernetes 对象时,请使用命令式的 kubectl delete 命令,因为它能给出可预测的结果。您可以在官方文档中了解更多关于资源声明式删除的信息,但在实践中,这是一种更具风险的方法:kubernetes.io/docs/tasks/manage-kubernetes-objects/declarative-config/#how-to-delete-objects.

  • 尽可能使用标签来语义化地描述您的组件:kubernetes.io/docs/concepts/configuration/overview/#using-labels.

关于清单文件的更多最佳实践可以在官方文档中找到:kubernetes.io/docs/concepts/configuration/overview/.

现在,让我们尝试通过部署一个类似上一节中的应用程序来演示这种方法。这次,我们将使用 Deployment 和 service 对象,它们将在单独的清单文件中定义——在实际场景中,您可能会将这两个清单文件组合成一个文件,但出于演示目的,将它们分开是有意义的。按照以下步骤部署应用程序:

  1. 打开 PowerShell 窗口。

  2. 确保您的集群没有运行上一节中的资源——您可以使用 kubectl get all 命令来检查并使用 kubectl delete 命令来删除它们。

  3. 为清单文件创建一个目录,例如declarative-demo

md .\declarative-demo
cd .\declarative-demo
  1. 创建包含部署定义的windows-example-deployment.yaml清单文件:
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: windows-example
  labels:
    app: sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: windows-example
  template:
    metadata:
      name: windows-example
      labels:
        app: windows-example
    spec:
      nodeSelector:
        "beta.kubernetes.io/os": windows
      containers:
      - name: windows-example
        image: mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809
        ports:
          - containerPort: 80
  1. 创建包含 LoadBalancer 服务定义的windows-example-service.yaml清单文件:
---
apiVersion: v1
kind: Service
metadata:
  name: windows-example
spec:
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 80
  selector:
    app: windows-example
  1. 使用kubectl apply命令在当前目录中应用清单文件。请注意,如果您有多级目录层次结构,您可以使用-R标志进行递归处理:
PS C:\src\declarative-demo> kubectl apply -f .\
deployment.apps/windows-example created
service/windows-example created
  1. 使用以下命令等待服务的外部 IP 可用:
PS C:\src\declarative-demo> kubectl get service -w
NAME              TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
kubernetes        ClusterIP      10.0.0.1      <none>        443/TCP          44h
windows-example   LoadBalancer   10.0.63.175   51.144.36.7   8080:30568/TCP   3m28s
  1. 使用您的网络浏览器导航到外部 IP 和端口8080

现在,让我们看看如何使用声明式方法对应用程序进行简单更改——我们想将 LoadBalancer 端口更改为9090

  1. 打开包含 LoadBalancer 服务定义的windows-example-service.yaml清单文件。

  2. spec.ports[0].port值修改为9090

  3. 保存清单文件。

  4. (可选但建议)使用kubectl diff命令验证您的更改。请记住,您需要安装并在$env:KUBECTL_EXTERNAL_DIFF环境变量中定义适当的diff工具;您可以在第六章中了解更多信息,与 Kubernetes 集群交互

kubectl diff -f .\
  1. 再次应用清单文件:
PS C:\src\declarative-demo> kubectl apply -f .\
deployment.apps/windows-example unchanged
service/windows-example configured
  1. 请注意,只有service/windows-example被检测为所需配置中的更改。

  2. 现在,您可以在网络浏览器中导航到外部 IP 地址和端口9090以验证更改。

  3. 如果您想删除当前目录中由清单文件创建的所有资源,可以使用以下命令:

kubectl delete -f .\

就是这样!正如您所看到的,声明式管理可能需要更多的样板配置,但最终,使用这种方法管理应用程序更加可预测和易于跟踪。

在管理在多个环境中运行的复杂应用程序时,考虑使用 Kustomization 文件(可与kubectl apply命令一起使用)或 Helm Charts。例如,使用 Kustomization 文件,您可以将配置文件组织在一个符合约定的目录结构中:kubectl.docs.kubernetes.io/pages/app_composition_and_deployment/structure_directories.html

在下一节中,我们将简要介绍关于在 Windows 节点上调度 Pod 的推荐做法。

在 Windows 节点上调度 Pods

要在具有特定属性的节点上调度 Pods,Kubernetes 为您提供了一些可能的选项:

  • 在 Pod 规范中使用nodeName。这是在给定节点上静态调度 Pods 的最简单形式,通常不建议使用。

  • 在 Pod 规范中使用nodeSelector。这使您有可能仅在具有特定标签值的节点上调度您的 Pod。我们在上一节中已经使用了这种方法。

  • 节点亲和性和反亲和性:这些概念扩展了nodeSelector方法,并提供了更丰富的语言来定义哪些节点是首选或避免为您的 Pod。您可以在官方文档中了解更多可能性:kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity

  • 节点污点和 Pod 容忍度:它们提供了与节点亲和性相反的功能-您将一个污点应用于给定节点(描述某种限制),Pod 必须具有特定的容忍度才能在被污染的节点上调度。

在混合 Windows/Linux 集群中调度 Pods 至少需要使用nodeSelector或一组带有nodeSelector的节点污点的组合。每个 Kubernetes 节点默认都带有一组标签,其中包括以下内容:

  • kubernetes.io/arch,描述节点的处理器架构,例如amd64arm:这也被定义为beta.kubernetes.io/arch

  • kubernetes.io/os,其值为linuxwindows:这也被定义为beta.kubernetes.io/os

您可以使用以下命令在 AKS Engine 集群中检查 Windows 节点(例如7001k8s011)的默认标签:

PS C:\src> kubectl describe node 7001k8s011
Name:               7001k8s011
Roles:              agent
Labels:             agentpool=windowspool2
 beta.kubernetes.io/arch=amd64
 beta.kubernetes.io/instance-type=Standard_D2_v3
 beta.kubernetes.io/os=windows
 failure-domain.beta.kubernetes.io/region=westeurope
 failure-domain.beta.kubernetes.io/zone=0
 kubernetes.azure.com/cluster=aks-engine-windows-resource-group
 kubernetes.azure.com/role=agent
 kubernetes.io/arch=amd64
 kubernetes.io/hostname=7001k8s011
 kubernetes.io/os=windows
 kubernetes.io/role=agent
 node-role.kubernetes.io/agent=
 storageprofile=managed
 storagetier=Standard_LRS

如果您的 Pod 规范中不包含nodeSelector,它可以在 Windows 和 Linux 节点上都可以调度-这是一个问题,因为 Windows 容器不会在 Linux 节点上启动,反之亦然。建议的做法是使用nodeSelector来可预测地调度您的 Pods,无论是 Windows 还是 Linux 容器。例如,在部署定义中,Pod 模板可能包含以下内容:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: windows-example
spec:
...
  template:
...
    spec:
      nodeSelector:
        "beta.kubernetes.io/os": windows
...

或者,您可以在 Kubernetes 的最新版本中使用"kubernetes.io/os": windows选择器。对于 Linux 容器,您需要指定"beta.kubernetes.io/os": linux"kubernetes.io/os": linux

当您将 Windows 节点添加到现有的大型 Linux-only 集群中时,这种方法可能会导致问题,使用 Helm Charts 或 Kubernetes Operators - 这些工作负载可能没有默认指定 Linux 节点选择器。为了解决这个问题,您可以使用污点和容忍:使用特定的NoSchedule污点标记您的 Windows 节点,并为您的 Pod 使用匹配的容忍。我们将使用带有os键和值Win1809的污点来实现这个目的。

对于污点 Windows 节点,您有两种可能性:

  • 在 kubelet 的注册级别上使用--register-with-taints='os=Win1809:NoSchedule'标志对节点进行污点。请注意,这种方法目前在 AKS Engine 中不可用,因为--register-with-taints不是用户可配置的 - 您可以在文档中阅读更多信息:github.com/Azure/aks-engine/blob/master/docs/topics/clusterdefinitions.md#kubeletconfig

  • 使用 kubectl 对节点进行污点。您可以使用以下命令添加一个污点:kubectl taint nodes <nodeName> os=Win1809:NoSchedule,并使用kubectl taint nodes 7001k8s011 os:NoSchedule-来删除它。

然后,您的部署定义将不得不为 Windows 指定适当的 Pod 节点选择器和污点容忍,以允许在 Windows 节点上调度:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: windows-example
spec:
...
  template:
...
    spec:
      nodeSelector:
        "beta.kubernetes.io/os": windows
      tolerations:
      - key: "os"
        operator: "Equal"
        value: "Win1809"
        effect: "NoSchedule"
...

在这种方法中,对于 Linux 容器,您不需要指定任何节点选择器或污点容忍。但是,如果可能的话,建议使用节点选择器方法而不使用节点污点,特别是如果您正在构建一个新的集群。

在下一节中,我们将看看如何访问您的应用程序。

访问您的应用程序

要访问在 Pod 中运行的应用程序,根据您的情况,您有一些可能性。在调试和测试场景中,您可以通过以下简单的方式访问您的应用程序:

  • 使用kubectl exec来创建一个临时的交互式 Pod。我们在之前的章节中使用了这种方法。

  • 使用kubectl proxy来访问任何服务类型。这种方法仅适用于 HTTP(S)端点,因为它使用 Kubernetes API 服务器提供的代理功能。

  • 使用kubectl port-forward。您可以使用这种方法来访问单个 Pod 或在部署或服务后面运行的 Pod。

如果您想要为生产环境的最终用户公开应用程序,您可以使用以下方法:

  • 具有 LoadBalancer 或 NodePort 类型的服务对象:我们已经在上一节中演示了如何使用 LoadBalancer 服务。

  • 使用 Ingress Controller 与 ClusterIP 类型的服务一起使用:这种方法减少了使用的云负载均衡器的数量(从而降低了运营成本),并在 Kubernetes 集群内执行负载均衡和路由。请注意,这种方法使用 L7 负载均衡,因此只能用于暴露 HTTP(S)端点。

您可以在《Kubernetes 网络》第五章中详细了解服务和 Ingress Controller。在本节的后面,我们将演示如何为演示应用程序使用 Ingress Controller。

您可以在官方文档中了解有关在集群中运行的应用程序的访问的更多信息:kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/#accessing-services-running-on-the-cluster

让我们首先演示如何使用kubectl proxykubectl port-forward。执行以下步骤:

  1. 打开一个 Powershell 窗口。

  2. 确保之前部分中的演示应用程序已部署,并且在集群中部署了一个端口为8080windows-example服务。

  3. 运行kubectl proxy命令:

PS C:\src\declarative-demo> kubectl proxy
Starting to serve on 127.0.0.1:8001
  1. 这将在本地主机的端口8001上暴露一个简单的代理服务器到远程 Kubernetes API 服务器。您可以自由地使用此端点使用 API,无需额外的身份验证。请注意,也可以使用原始 API 而不使用代理,但那样您就必须自己处理身份验证(kubernetes.io/docs/tasks/administer-cluster/access-cluster-api/)。

  2. 您的服务将在http://<proxyEndpoint>/api/v1/namespaces/<namespaceName>/services/[https:]<serviceName>[:portName]/proxy上可用。在我们的情况下,导航到http://127.0.0.1:8001/api/v1/namespaces/default/services/windows-example/proxy/。这种方法适用于任何服务类型,包括仅内部 ClusterIPs。

  3. 终止kubectl proxy进程。

  4. 现在,执行以下kubectl port-forward命令:

PS C:\src\declarative-demo> kubectl port-forward service/windows-example 5000:8080
Forwarding from 127.0.0.1:5000 -> 80
Forwarding from [::1]:5000 -> 80
  1. 这将把来自您的本地主机5000端口的任何网络流量转发到windows-example服务的8080端口。例如,您可以在 Web 浏览器中导航到http://127.0.0.1:5000/。请注意,这种方法也适用于 HTTP(S)以外的不同协议。

  2. 终止kubectl port-forward进程。

现在,让我们看看如何使用 Ingress Controller 来访问演示应用程序。使用 Ingress 是高度可定制的,有多个可用的 Ingress Controllers——我们将演示在 AKS Engine 混合集群上快速启动和运行ingress-nginxwww.nginx.com/products/nginx/kubernetes-ingress-controller)。请注意,这种方法将 Ingress Controllers 的部署限制在 Linux 节点上——您将能够为运行在 Windows 节点上的服务创建 Ingress 对象,但所有的负载均衡将在 Linux 节点上执行。按照以下步骤:

  1. 修改windows-example-service.yaml清单文件,使其具有type: ClusterIPport: 80,并且没有targetPort
apiVersion: v1
kind: Service
metadata:
  name: windows-example
spec:
  type: ClusterIP
  ports:
  - protocol: TCP
    port: 80
  selector:
    app: windows-example
  1. 将您的修改应用到集群中:
kubectl apply -f .\
  1. 应用官方的通用清单文件用于 ingress-nginx,它在 Linux 节点上创建一个具有一个副本的部署:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
  1. 申请官方的云特定清单文件用于 ingress-nginx。这将创建一个 LoadBalancer 类型的服务,用于 Ingress Controller:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/cloud-generic.yaml
  1. 等待 Ingress Controller 服务接收外部 IP 地址。外部 IP 地址104.40.133.125将用于所有配置在此 Ingress Controller 后运行的服务:
PS C:\src\declarative-demo> kubectl get service -n ingress-nginx -w
NAME            TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)                      AGE
ingress-nginx   LoadBalancer   10.0.110.35   104.40.133.125   80:32090/TCP,443:32215/TCP   16m
  1. 创建windows-example-ingress.yaml清单文件并定义 Ingress 对象。我们的应用程序的windows-example服务将在<ingressLoadBalancerIp>/windows-example路径下注册:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: windows-example-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
  - http:
      paths:
      - path: /windows-example(/|$)(.*)
        backend:
          serviceName: windows-example
          servicePort: 80
  1. 应用更改:
kubectl apply -f .\
  1. 导航到http://104.40.133.125/windows-example来测试 Ingress 定义。

当然,您可以为不同的服务创建多个 Ingress 对象,具有复杂的规则。一个经验法则是,尽可能使用 Ingress Controller 来公开您的 HTTP(S)端点,并为其他协议使用专用的 LoadBalancer 服务。

现在,让我们看看如何扩展您的应用程序!

扩展应用程序

在生产场景中,您肯定需要扩展您的应用程序 - 这就是 Kubernetes 强大之处;您可以手动扩展您的应用程序,也可以使用自动缩放。让我们首先看看如何执行部署的手动扩展。您可以通过命令或声明性地执行。要使用 PowerShell 中的命令执行扩展操作,请执行以下步骤:

  1. 执行 kubectl scale 命令,将 windows-example 部署扩展到三个副本:
PS C:\src\declarative-demo> kubectl scale deployment/windows-example --replicas=3
deployment.extensions/windows-example scaled
  1. 现在观察 Pods 如何被添加到您的部署中:
PS C:\src\declarative-demo> kubectl get pods -w
NAME READY STATUS RESTARTS AGE
windows-example-5cb7456474-5ndrm 0/1 ContainerCreating 0 8s
windows-example-5cb7456474-v7k84 1/1 Running 0 23m
windows-example-5cb7456474-xqp86 1/1 Running 0 8s

您也可以以声明性的方式执行类似的操作,这通常是推荐的。让我们进一步将应用程序扩展到四个副本:

  1. 编辑 windows-example-deployment.yaml 清单文件,并将 replicas 修改为 4

  2. 保存清单文件并应用更改:

PS C:\src\declarative-demo> kubectl apply -f .\
deployment.apps/windows-example configured
ingress.networking.k8s.io/windows-example-ingress unchanged
service/windows-example unchanged
  1. 再次使用 kubectl get pods -w 命令观察应用程序如何扩展。

Kubernetes 的真正力量在于自动缩放。我们将在第十一章 配置应用程序使用 Kubernetes 功能 中更详细地介绍自动缩放,因此在本节中,我们只会简要概述如何使用命令来执行它:

  1. 首先,您需要为部署中的 pod 模板配置 CPU 资源限制 - 将其设置为一个小值,例如 100m。如果没有 CPU 资源限制,自动缩放将无法正确应用扩展策略:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: windows-example
...
spec:
...
    spec:
...
      containers:
      - name: windows-example
...
        resources:
          limits:
            cpu: 100m
          requests:
            cpu: 100m
  1. 应用修改:
kubectl apply -f .\
  1. 执行以下 kubectl autoscale 命令:
kubectl autoscale deployment/windows-example --cpu-percent=15 --min=1 --max=5
  1. 这将在集群中创建一个水平 Pod 自动缩放器(HPA)对象,采用默认算法,最少 1 个副本,最多 5 个副本,并基于目标 CPU 使用率的 15% 的限制进行配置。

  2. 使用以下命令来检查 HPA 的状态:

kubectl describe hpa windows-example
  1. 您可以尝试通过频繁刷新应用程序网页向您的 Pod 添加一些 CPU 负载。请注意,如果您使用 Ingress,您将命中 Ingress 控制器的缓存,因此在这种情况下 CPU 使用率可能不会增加。

  2. 过一段时间,您会看到自动缩放开始并添加更多副本。当您减少负载时,部署将被缩减。您可以使用 kubectl describe 命令来检查时间线:

PS C:\src\declarative-demo> kubectl describe hpa windows-example
...
 Normal   SuccessfulRescale             11m                horizontal-pod-autoscaler  New size: 3; reason: cpu resource utilization (percentage of request) above target
 Normal   SuccessfulRescale             4m17s              horizontal-pod-autoscaler  New size: 1; reason: All metrics below target
  1. 使用此命令删除 HPA 对象以关闭自动缩放:
kubectl delete hpa windows-example

对于托管的 AKS 实例,可以利用节点级自动缩放功能(docs.microsoft.com/en-us/azure/aks/cluster-autoscaler),为您的工作负载带来了另一个可伸缩性维度。此外,您可以考虑使用 Azure 容器实例(ACI)与 AKS 工作负载(docs.microsoft.com/en-us/azure/architecture/solution-ideas/articles/scale-using-aks-with-aci)。

恭喜!您已成功在 AKS Engine 混合 Kubernetes 集群上部署和自动缩放了您的第一个应用程序。

总结

本章简要介绍了如何在 AKS Engine 混合集群上部署和管理运行 Windows 容器应用程序。您学会了命令式和声明式集群配置管理的区别以及何时使用它们。我们已经使用了这两种方法来部署演示应用程序-现在您知道推荐的声明式方法比使用命令式命令更容易,更不容易出错。接下来,您将学习如何可预测地在 Windows 节点上安排 Pod,并如何处理将 Windows 容器工作负载添加到现有 Kubernetes 集群。最后,我们展示了如何访问在 Kubernetes 中运行的应用程序,供最终用户和开发人员使用,以及如何手动和自动扩展应用程序。

在下一章中,我们将利用所有这些新知识来将一个真正的.NET Framework 应用程序部署到我们的 Kubernetes 集群!

问题

  1. Kubernetes 对象的命令式和声明式管理有什么区别?

  2. 何时推荐使用命令式命令?

  3. 如何查看本地清单文件和当前集群状态之间的更改?

  4. 在混合集群中安排 Pod 的推荐做法是什么?

  5. kubectl proxykubectl port-forward命令之间有什么区别?

  6. 何时可以使用 Ingress Controller?

  7. 如何手动扩展部署?

您可以在本书的评估中找到这些问题的答案。

进一步阅读

第十章:部署 Microsoft SQL Server 2019 和 ASP.NET MVC 应用程序

之前的章节为您提供了一个部署和操作混合 Windows/Linux Kubernetes 集群的瑞士军刀,现在,您已经掌握了部署真实 Windows 容器应用程序到 Kubernetes 集群的所有基本知识。本章将重点演示如何处理使用 C# .NET Framework 4.8 和 ASP.NET MVC 5 编写的简单投票应用程序的容器化和部署,其中 Microsoft SQL Server 2019 用于持久层。技术栈的选择可能看起来是传统的(为什么不使用.NET Core?!),但这是有意为之——如果您正在考虑在 Kubernetes 中使用 Windows 容器,那么您很可能需要经典的.NET Framework 运行时,因为您还没有准备好迁移到.NET Core。

迁移现有应用程序到 Kubernetes 的主题是广泛的,本书不会对其进行全面覆盖。有许多关于这一过程的最佳实践文档,但我们将专注于基本方法,主要是为了演示部署而不是专注于.NET Framework 应用程序的实现和迁移。本章的目标是展示以下内容:

  • 如何快速将 Windows .NET Framework 应用程序容器化

  • 如何注入环境配置,如 SQL 连接字符串

  • Windows 容器日志的推荐方法

  • 如何远程调试应用程序

更准确地说,在本章中,我们将涵盖以下主题:

  • 创建并发布 ASP.NET MVC 应用程序到 Docker Hub

  • 准备Azure Kubernetes 服务引擎AKS 引擎

  • 部署故障转移 Microsoft SQL Server 2019

  • 部署 ASP.NET MVC 应用程序

  • 访问应用程序

  • 扩展应用程序

  • 调试应用程序

技术要求

对于本章,您将需要以下内容:

  • 安装 Windows 10 专业版、企业版或教育版(1903 版或更高版本,64 位)。

  • Microsoft Visual Studio 2019 社区版(或其他任何版本),如果您想编辑应用程序的源代码并对其进行调试。Visual Studio CodeVS Code)对经典.NET Framework 的支持有限。

  • Azure 账户。

  • 使用 AKS 引擎部署的 Windows/Linux Kubernetes 集群。

要跟着做,您需要自己的 Azure 账户,以便为 Kubernetes 集群创建 Azure 资源。如果您之前还没有为前几章创建账户,您可以在这里了解如何获得用于个人使用的有限免费账户:azure.microsoft.com/en-us/free/

使用 AKS Engine 部署 Kubernetes 集群已在第八章中进行了介绍,部署混合 Azure Kubernetes 服务引擎集群

您可以从官方 GitHub 存储库下载本书章节的最新代码示例,网址为:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter10

创建并发布一个 ASP.NET MVC 应用程序到 Docker Hub

为了演示部署一个真实的 Windows 容器应用程序,我们将为一个投票应用程序创建一个 Docker 镜像,这是一个用于创建调查的小型 C# .NET Framework 4.8 Web 应用程序。该应用程序使用经典的 ASP.NET MVC 5 堆栈实现,因为它最适合演示如何处理 Windows 应用程序的容器化。传统的.NET Framework 应用程序,特别是企业级应用程序,严重依赖于仅在 Windows 上可用的功能,比如Windows Communication FoundationWCF)。在许多情况下,您可能很幸运地轻松迁移到.NET Core,并使用 Linux 容器来托管您的应用程序,但对于.NET Framework 堆栈的某些部分,甚至在.NET 5 中也可能永远不会发生。

关于我们的投票应用程序,有一些假设,如下:

您可以在该书的官方 GitHub 存储库中找到应用程序源代码,网址为github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter10/01_voting-application-src。要打开VotingApplication.sln解决方案文件,您需要 Visual Studio 2019。也可以通过使用docker build命令执行构建,如下一小节所述。在本节结束时,您将拥有一个用于投票应用程序的 Docker 镜像,准备在 Kubernetes 中使用。您可以按照步骤进行,或选择使用 Docker Hub 上提供的现成镜像,网址为hub.docker.com/repository/docker/packtpubkubernetesonwindows/voting-application

使用环境变量注入配置

在开发容器友好的应用程序时,您需要考虑如何注入配置数据,例如数据库连接字符串。一个经验法则是,您不应该将任何地址、用户名、密码或连接字符串硬编码到您的代码中。您应该始终能够在运行时注入这样的配置,并且一般来说,非容器化应用程序也是如此。Kubernetes 为您提供了多种方法来注入运行时配置,如下所示:

  • 向容器命令传递参数

  • 为容器定义系统环境变量

  • 将 ConfigMaps 或 Secrets 挂载为容器卷

  • 可选地使用 PodPresets 封装所有内容

您可以在官方文档中了解更多关于它们的信息(kubernetes.io/docs/tasks/inject-data-application/)。重要的是,所有这些特性都使用标准的操作系统级原语,如文件或环境变量,与容器化应用程序集成。这意味着,如果您设计得当,您可以在 Kubernetes 内外不做任何更改地使用它。

我们将演示如何使用环境变量将 MSSQL Server 连接字符串注入到我们的应用程序中。这种方法是最简单的,但它有一个重要的限制——当容器正在运行时,您无法修改容器的环境变量。一旦设置了变量,它将在整个容器生命周期内保持相同的值。如果您需要能够在不重新启动容器的情况下修改配置,您应该看一下 ConfigMaps(与 Secrets 结合使用),这在下一章节中描述:第十一章,配置应用程序以使用 Kubernetes 功能

我们的投票应用程序使用VotingApplicationContextFactory类来为Model-View-Controller(MVC)控制器创建 EF DbContext。让我们来看看这个类的Create()方法(可在github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter10/01_voting-application-src/Factories/VotingApplicationContextFactory.cs找到)。

public object Create(IContext context)
{
 var connectionString = Environment.GetEnvironmentVariable("CONNECTIONSTRING_VotingApplication");
 if (!string.IsNullOrEmpty(connectionString))
 {
 var safeConnectionString = SanitizeConnectionString(connectionString);
 this.log.Info("Using custom connection string provided by environment variable: {0}", safeConnectionString);
 return new VotingApplicationContext(connectionString);
 }

 this.log.Info("Using default connection string");
 return new VotingApplicationContext();
}

以下是您可以使用的一种常见模式,特别是在 Linux 世界中,那里更常依赖于环境变量:

  1. 检查您选择的环境变量CONNECTIONSTRING_VotingApplication是否已定义。

  2. 如果是,使用变量中的重写连接字符串创建 EF DbContext。

  3. 如果没有,使用标准连接字符串创建 EF DbContext。在这种情况下,它将从Web.config应用程序文件中检索。

您可以遵循这种模式,特别是当您不使用自定义配置文件时。这种解决方案为您提供了很大的灵活性,您也可以在不使用容器运行应用程序时使用它!

另一种方法是将整个Web.config文件作为 Kubernetes ConfigMap 对象注入。我们将在下一章中探讨这种可能性。

这显示了一个重要的原则,即在容器化任何应用程序时应该考虑应用程序(系统)的外部接口以及它如何与外部世界通信。这是您可以影响或监视在容器中运行的应用程序的唯一方法。提供和注入配置是您的应用程序的外部接口之一。同样,日志记录为您的应用程序定义了一个输出接口,让我们看看您如何在 Windows 容器中处理这个问题。

为 Windows 容器配置日志记录日志监视器

Kubernetes 本身提供了简单的工具来浏览 Pod 容器日志。通常情况下,您将不得不实现一个良好的集群级日志记录解决方案,例如使用 Elasticsearch、Logstash、Kibana 堆栈或使用 Azure Log Analytics(如前几章中简要演示的)。官方文档对日志记录解决方案的可能架构进行了很好的概述:kubernetes.io/docs/concepts/cluster-administration/logging/。在所有情况下,您都需要将容器中的应用程序日志暴露给外部世界。从高层次来看,有三种主要方法:

  • 使用容器的标准输出(stdout)和标准错误(stderr)入口点,并让容器运行时处理日志记录。稍后可以使用节点级别的日志代理(例如 Fluentd、Elastic Beats 或 Logstash)来转发日志到任何外部日志解决方案。如果您的容器化应用程序默认将所有内容写入控制台输出,这种方法特别有效。

  • 在您的应用程序 Pod 中使用额外的辅助容器,该容器从文件系统、事件日志或其他来源收集日志,并将其公开为 stdout 或直接传输到外部日志解决方案。如果您的应用程序将日志记录到容器内的多个目的地,这种方法非常有用。

  • 将日志流嵌入到应用程序本身中。例如,在 C#应用程序中,您可以使用 log4net 和专用的 Elasticsearch appender (github.com/ptylenda/log4net.ElasticSearch.Async) 来将日志流式传输到您的 Elasticsearch 集群。这种方法的限制最多——它会对外部日志系统产生严重依赖,并且可能会对性能产生影响,这种影响很难与应用程序本身的工作负载分离。

对于 Windows 应用程序,将日志记录到 stdout 并不常见,特别是对于旧应用程序以及在使用 Internet Information Services (IIS) 托管您的 Web 应用程序时。在大多数情况下,对于 Windows 来说,更常见的是使用 Event Tracing for Windows (ETW)、事件日志或自定义日志文件。例如,我们的投票应用程序是使用 IIS 进行托管的。此外,在容器化模式下运行时,IIS 不提供公开应用程序的 stdout 的功能。您必须依赖事件日志或自己的日志文件。除此之外,IIS 本身会在标准位置c:\inetpub\logs中公开额外的应用程序日志,并将其自己的事件流传输到 ETW。

您可以以两种方式处理投票应用程序的日志收集:

我们将使用日志监视器,因为它易于集成和配置。应用程序的 Dockerfile 的详细信息将在下一小节中显示。假设启动应用程序(在本例中为 IIS)的命令是C:\ServiceMonitor.exe w3svc,使用 Log Monitor 的一般模式是以以下方式自定义 Dockerfile:

WORKDIR /LogMonitor
COPY LogMonitor.exe LogMonitorConfig.json .
SHELL ["C:\\LogMonitor\\LogMonitor.exe", "powershell.exe"]

ENTRYPOINT C:\ServiceMonitor.exe w3svc

我们应用程序的LogMonitoringConfig.json文件(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter10/01_voting-application-src/LogMonitorConfig.json)具有以下 JSON 配置:

{
  "LogConfig": {
    "sources": [
      {
        "type": "EventLog",
        "startAtOldestRecord": true,
        "eventFormatMultiLine": false,
        "channels": [
          {
            "name": "system",
            "level": "Error"
          }
        ]
      },
      {
        "type": "EventLog",
        "startAtOldestRecord": true,
        "eventFormatMultiLine": false,
        "channels": [
          {
            "name": "VotingApplication",
            "level": "Verbose"
          }
        ]
      },
      {
        "type": "File",
        "directory": "c:\\inetpub\\logs",
        "filter": "*.log",
        "includeSubdirectories": true
      },
      {
        "type": "ETW",
        "providers": [
          {
            "providerName": "IIS: WWW Server",
            "ProviderGuid": "3A2A4E84-4C21-4981-AE10-3FDA0D9B0F83",
            "level": "Information"
          },
          {
            "providerName": "Microsoft-Windows-IIS-Logging",
            "ProviderGuid ": "7E8AD27F-B271-4EA2-A783-A47BDE29143B",
            "level": "Information",
            "keywords": "0xFF"
          }
        ]
      }
    ]
  }
}

此配置文件订阅日志监视器到系统日志和 Windows 事件日志中的VotingApplication日志,监视C:\inetpub\logs中的日志,并收集 IIS 的 ETW 数据。事件日志中的VotingApplication日志包含我们应用程序中由 Serilog 生成的所有日志。这在NinjectWebCommon类中配置(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter10/01_voting-application-src/App_Start/NinjectWebCommon.cs),在那里我们初始化了日志记录器的输出,如下所示:

private static void RegisterServices(IKernel kernel)
{
    Log.Logger = new LoggerConfiguration()
        .ReadFrom.AppSettings()
        .Enrich.FromLogContext()
        .WriteTo.EventLog(source: "VotingApplication", logName: "VotingApplication", manageEventSource: false)
        .CreateLogger();

    kernel.Bind<VotingApplicationContext>().ToProvider(typeof(VotingApplicationContextFactory)).InRequestScope();
    kernel.Bind<IDateTimeProvider>().To<DateTimeProvider>().InRequestScope();
}

请注意,由于 Windows 容器不在特权模式下运行,我们无法自动在事件日志中创建日志(manageEventSource: false)。这必须在构建时在 Dockerfile 中完成。

通过这个设置,我们的投票应用程序将把我们自己的所有日志,以及系统和 IIS 的日志一起打印到容器的 stdout 中。这意味着您可以使用docker logs命令(在运行独立容器时)或kubectl logs命令轻松地对它们进行调查。如果与 Azure Log Analytics 集成,您的日志将可以使用 Kusto 进行查询。

创建 Dockerfile

下一步是为我们的应用程序准备一个 Dockerfile。您可以查看官方文档,了解如何在 Dockerfile 中构建.NET Framework 应用程序的方法,网址为github.com/microsoft/dotnet-framework-docker/tree/master/samples/dotnetapp。我们的 Dockerfile 必须包括以下步骤:

  1. 恢复 NuGet 软件包。

  2. 构建应用程序,最好使用发布配置文件到本地文件系统。

  3. 复制用于应用 EF 迁移的工具(由 EF NuGet 软件包提供)。

  4. 在事件日志中创建VotingApplication日志。

  5. 复制日志监视器二进制文件和配置。

  6. 将投票应用程序二进制文件复制到C:\inetpub\wwwroot以进行 IIS 托管。

我们需要更深入地讨论 EF 迁移的话题。在没有应用程序停机时间并且应用程序有多个副本的情况下应用 EF 数据库迁移是一个复杂的任务。您需要确保迁移可以回滚,并且数据库架构与旧版本和新版本的应用程序完全兼容。换句话说,不兼容的更改,比如重命名,必须特别处理,以使它们在各个步骤之间具有向后兼容性。这个过程的框架可能如下所示 - 例如,对于实体的列重命名:

  1. 应用添加具有新名称的新列的数据库迁移。

  2. 推出一个新版本的应用程序,对旧列和新列进行写入。读取应该使用旧列进行,因为它始终具有正确的数据。

  3. 执行一个从旧列复制数据到新列的作业。

  4. 推出一个新版本的应用程序,从新列中读取。

  5. 推出一个新版本的应用程序,只写入新列。

  6. 应用数据库迁移,删除旧列。

正如您所看到的,为在 Kubernetes 中运行的应用程序正确处理数据库迁移而不中断需要严格的规则和兼容性/回滚测试 - 我们已经将这个话题带到您的注意中,但详细的解决方案不在本书的范围之内。Spring 有一篇很好的文章解释了如何解决这个问题(spring.io/blog/2016/05/31/zero-downtime-deployment-with-a-database),Weaveworks 也有一篇专门针对 Kubernetes 的文章:www.weave.works/blog/how-to-correctly-handle-db-schemas-during-kubernetes-rollouts

为了应用迁移,我们将使用相同的 Docker 镜像 - EF 数据库迁移是使用应用程序程序集和 EF 命令行工具应用的,我们将在镜像中提供。然后,迁移(和数据库种子)将使用适合运行一次性任务的 Kubernetes 作业来运行。在实际情况下,这应该被安排为您的持续集成/持续部署CI/CD)过程的一部分,伴随着 Kubernetes 部署的推出。

投票应用程序包含一个名为Dockerfile.production的 Dockerfile(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter10/01_voting-application-src/Dockerfile.production),它的层基于我们刚刚总结的内容。让我们逐步分析它:

  1. Dockerfile 定义了一个多阶段构建,这意味着在整个构建过程中使用了多个基础镜像。第一阶段是 Web 应用程序构建,使用了一个mcr.microsoft.com/dotnet/framework/sdk镜像。这个镜像包含了所有不需要运行时的.NET Framework 构建工具。其代码如下所示:
FROM mcr.microsoft.com/dotnet/framework/sdk:4.8-windowsservercore-ltsc2019 AS build
ARG PUBLISH_PROFILE=DockerPublishProfile.pubxml
ARG BUILD_CONFIG=Release

WORKDIR /app

COPY *.sln ./
COPY *.csproj ./
COPY *.config ./
RUN nuget restore

COPY . .
RUN msbuild /p:DeployOnBuild=true /p:PublishProfile=$env:PUBLISH_PROFILE /p:Configuration=$env:BUILD_CONFIG

这些层的组织方式使得在构建过程中最大化了层缓存的利用——例如,只有在特定解决方案配置文件更改时才运行nuget restore

  1. 构建过程由标准的msbuild命令执行,使用了一个名为DockerPublishProfile.pubxml的专用发布配置文件,其形式如下:
<Project ToolsVersion="4.0" >
  <PropertyGroup>
    <WebPublishMethod>FileSystem</WebPublishMethod>
    <PublishProvider>FileSystem</PublishProvider>
    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
    <LastUsedPlatform>Any CPU</LastUsedPlatform>
    <SiteUrlToLaunchAfterPublish />
    <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
    <ExcludeApp_Data>False</ExcludeApp_Data>
    <publishUrl>obj\Docker\publish</publishUrl>
    <DeleteExistingFiles>True</DeleteExistingFiles>
  </PropertyGroup>
</Project>

原则上,它执行FileSystem发布到obj\Docker\publish,这稍后将用于创建最终镜像。

  1. 接下来,我们基于专门用于运行时场景的mcr.microsoft.com/dotnet/framework/aspnet镜像开始第二个和最后一个构建阶段,如下所示:
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2019 AS runtime

WORKDIR /ef6
COPY --from=build /app/packages/EntityFramework.6.3.0/tools/net45/any/ .

在第一步中,我们执行 EF6 迁移命令行工具的复制,这些工具是通过 EF NuGet 包提供的。关键在于使用--from=build参数从上一个阶段复制。

  1. 接下来是为我们的投票应用程序创建一个专用的事件日志(这个要求在前面的小节中提到过),如下所示:
RUN powershell.exe -Command New-EventLog -LogName VotingApplication -Source VotingApplication
  1. 复制LogMonitor二进制文件和配置,同时覆盖容器的 shell 命令,如下所示:
WORKDIR /LogMonitor
ADD https://github.com/microsoft/windows-container-tools/releases/download/v1.0/LogMonitor.exe .
COPY --from=build /app/LogMonitorConfig.json .
SHELL ["C:\\LogMonitor\\LogMonitor.exe", "powershell.exe"]
  1. 将前一个阶段的build工件复制到C:\inetpub\wwwroot的 IIS 应用程序目录中,如下所示:
WORKDIR /inetpub/wwwroot
COPY --from=build /app/obj/Docker/publish/. .
  1. 最后,将镜像的默认入口点定义为启动 IIS 服务的ServiceMonitor.exe。这是一个标准的方法,在mcr.microsoft.com/dotnet/framework/aspnet基础镜像中可以看到。唯一的区别是整个进程树将在日志监视器的监督下运行。其代码如下所示:
ENTRYPOINT C:\ServiceMonitor.exe w3svc

就是这样!Dockerfile 定义了 ASP.NET MVC 应用程序的完整构建过程——您可以选择性地扩展它,添加一个测试阶段,执行适当的测试。现在,让我们构建镜像并将其推送到镜像注册表。

构建和推送 Docker 镜像

这个过程的确切细节已在第三章中进行了介绍,使用容器镜像。简而言之,您可以在这里使用两种方法:

  1. 在本地机器上执行镜像的手动构建并将其推送到公共 Docker Hub。目前,在 Docker Hub 上无法为 Windows 容器镜像设置自动构建。

  2. 如果您有兴趣将自动构建和 GitHub 挂钩集成到您的应用程序中,您可以使用Azure 容器注册表ACR),如前面提到的章节中所述。

为了简单起见,我们将执行手动构建并将镜像推送到 Docker Hub。在实际情况下,您应该至少在 CI/CD 流水线中使用带有 GitHub 集成的 ACR。让我们执行 Docker 镜像的构建——在示例中,我们将使用packtpubkubernetesonwindows/voting-application镜像仓库名称,但如果您在跟随操作,应该使用您自己的<dockerId>/voting-application仓库。执行以下步骤:

  1. 打开 PowerShell 窗口,导航到主voting-application源目录。

  2. 使用以下命令执行 Docker 构建(记住最后的句点,它指定了构建上下文目录):

docker build -t packtpubkubernetesonwindows/voting-application -f .\Dockerfile.production .
  1. 等待构建完成,并相应地标记镜像。这对于 Kubernetes 部署至关重要,因为我们可以指定要推出的镜像的特定版本(使用最新版本会产生歧义,通常不建议使用)。建议使用语义版本控制,如第三章中所述的使用容器镜像,并且在以下代码块中进行了说明:
docker tag packtpubkubernetesonwindows/voting-application:latest packtpubkubernetesonwindows/voting-application:1.0.0
docker tag packtpubkubernetesonwindows/voting-application:latest packtpubkubernetesonwindows/voting-application:1.0
docker tag packtpubkubernetesonwindows/voting-application:latest packtpubkubernetesonwindows/voting-application:1
  1. 将所有标记推送到镜像仓库,如下所示:
docker push packtpubkubernetesonwindows/voting-application
  1. 现在,您可以验证 Docker Hub 页面上的标记是否正确可见,例如,hub.docker.com/repository/docker/packtpubkubernetesonwindows/voting-application/tags?page=1

此时,我们的 Docker 镜像可以在本地使用(您需要使用环境变量提供有效的连接字符串到 SQL Server),也可以在 Kubernetes 中使用。让我们开始准备 AKS Engine 集群部署!

准备 AKS Engine

如果您已经按照第八章 部署混合 Azure Kubernetes 服务引擎集群创建了一个混合 Windows/Linux Kubernetes 集群,并且已经准备就绪,您可以在本节中验证集群的拓扑是否符合要求。如果您还没有部署 AKS Engine 集群,我们将在书籍的 GitHub 存储库中提供一个快速部署的 PowerShell 脚本。

我们的投票应用程序可以托管在尽可能小的混合 Windows/Linux 集群上,一个 Linux 主节点和一个 Windows 节点,其中 Linux 主节点充当常规节点。然而,为了充分演示部署的原则,我们将使用一个类似于生产环境的集群:一个运行在虚拟机规模集VMSS高可用性HA)模式下的 Linux 主节点,两个 Linux 节点和两个 Windows 节点。我们在上一章中已经使用了这种配置来部署 AKS Engine 集群。为了快速从头部署 AKS Engine 集群,您可以执行以下步骤:

  1. 从书籍的 GitHub 存储库下载以下 PowerShell 脚本:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter08/01_aks-engine/01_CreateAKSEngineClusterWithWindowsNodes.ps1

  2. 在 PowerShell 窗口中,使用适当的参数执行脚本,如下所示:

.\01_CreateAKSEngineClusterWithWindowsNodes.ps1 `
 -azureSubscriptionId <subscriptionId> `
 -dnsPrefix <globallyUniqueDnsPrefix> `
 -windowsPassword <windowsNodesPassword>
  1. 该脚本将在 Azure 中使用aks-engine-windows-resource-group资源组将集群部署到 West Europe 位置。如果 AKS Engine 部署出现问题,您可以尝试指定不同的区域,例如-azureLocation westus

  2. 当部署完成后,您需要确保您的默认 kubeconfig 包含新集群的上下文。您可以通过使用以下命令快速合并由 AKS Engine 为 West Europe 位置生成的 kubeconfig 与默认 kubeconfig(在覆盖默认配置文件之前,请记得仔细检查config_new文件的内容,以避免任何丢失):

$env:KUBECONFIG=".\_output\<globallyUniqueDnsPrefix>\kubeconfig\kubeconfig.westeurope.json;$env:USERPROFILE\.kube\config"
kubectl config view --raw > $env:USERPROFILE\.kube\config_new
 Move-Item -Force $env:USERPROFILE\.kube\config_new $env:USERPROFILE\.kube\config
  1. 在新的 PowerShell 窗口中,通过运行以下命令来验证您是否能够访问集群,例如:
kubectl get nodes

运行这种规模的 AKS Engine 集群可能成本高昂,因此您应该始终检查虚拟机VM)托管的预估成本。如果您不再需要该集群,可以使用az group delete --name aks-engine-windows-resource-group --yes命令来简单地删除它,也可以选择提供--no-wait参数。

此时,您已经准备好运行 Microsoft SQL Server 2019 和投票应用程序的集群,所以让我们继续!

部署故障转移 Microsoft SQL Server 2019

从 MSSQL Server 2017 开始,可以将其托管在 Linux Docker 容器中。由于我们的应用程序需要 MSSQL Server 进行数据持久化,因此我们将部署最新版本的 MSSQL Server 2019 到我们的 Kubernetes 集群中。目前,可以以以下两种模式将 MSSQL Server 部署到 Kubernetes 中:

  1. 一个单节点实例,故障转移由 Kubernetes 部署和 Azure 磁盘持久卷保证。

  2. 使用专用的 Kubernetes 操作员(kubernetes.io/docs/concepts/extend-kubernetes/operator/)的多节点 HA 集群。

第二种模式在社区技术预览CTP)2.0 版本中作为预览版本发布(cloudblogs.microsoft.com/sqlserver/2018/12/10/availability-groups-on-kubernetes-in-sql-server-2019-preview/),但目前,在一般可用性GA)版本中,Docker 镜像和 Kubernetes 清单不兼容。如果您感兴趣,可以在github.com/microsoft/sql-server-samples/tree/master/samples/features/high%20availability/Kubernetes/sample-manifest-files上检查此类部署的官方清单文件。

因此,我们将以更简单的单节点故障转移模式部署 SQL Server。要做到这一点,请执行以下步骤:

  1. 打开 PowerShell 窗口。

  2. 为新的 Kubernetes 命名空间创建一个dev.yaml清单文件,内容如下,并使用kubectl apply -f .\dev.yaml命令应用它:

kind: Namespace
apiVersion: v1
metadata:
  name: dev
  labels:
    name: dev
  1. 为使用 Azure Disk provisioner 的 Kubernetes 存储类创建一个storage-class.yaml清单文件,并使用kubectl apply -f .\storage-class.yaml命令应用它,如下所示:
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: azure-disk
provisioner: kubernetes.io/azure-disk
parameters:
  storageaccounttype: Standard_LRS
  kind: Managed
  1. 创建一个pvc.yaml清单文件,为 SQL Server 实例定义一个mssql-data持久卷索赔PVC)。这个 PVC 将用于在容器中的/var/opt/mssql中挂载数据。使用kubectl apply -f .\pvc.yaml命令应用该清单,如下所示:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  namespace: dev
  name: mssql-data
  annotations:
    volume.beta.kubernetes.io/storage-class: azure-disk
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
  1. 定义一个 Kubernetes mssql Secret,其中包含 SQL Server 的系统管理员SA)用户密码,使用您自己的安全密码,如下所示:
kubectl create secret generic -n dev mssql --from-literal=SA_PASSWORD="S3cur3P@ssw0rd"
  1. 创建一个sql-server.yaml清单文件,为 SQL Server 定义 Kubernetes 部署,如下所示:
kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: dev
  name: mssql-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mssql
  template:
    metadata:
      labels:
        app: mssql
    spec:
      terminationGracePeriodSeconds: 10
      initContainers:
      - name: volume-mount-permissions-fix  # (1)
        image: busybox
        command: ["sh", "-c", "chown -R 10001:0 /var/opt/mssql"]
        volumeMounts:
        - name: mssqldb
          mountPath: /var/opt/mssql
      containers:
      - name: mssql
        image: mcr.microsoft.com/mssql/server:2019-GA-ubuntu-16.04
        ports:
        - containerPort: 1433
        env:
        - name: MSSQL_PID  # (2)
          value: "Developer"
        - name: ACCEPT_EULA
          value: "Y"
        - name: MSSQL_SA_PASSWORD  # (3)
          valueFrom:
            secretKeyRef:
              name: mssql
              key: SA_PASSWORD  # (4)
        volumeMounts:  # (5)
        - name: mssqldb
          mountPath: /var/opt/mssql
      volumes:
      - name: mssqldb
        persistentVolumeClaim:
          claimName: mssql-data
      nodeSelector:
        "beta.kubernetes.io/os": linux

这个清单文件有几个重要部分,如下所示:

  1. 首先,我们需要一个额外的volume-mount-permissions-fix初始化容器,这是为了确保在挂载 PVC 后,目录对于 SQL Server 有适当的访问权限——该容器将在常规 Pod 容器创建之前运行。这是如何使用初始化容器的一个很好的例子。

  2. 其次,我们需要接受最终用户许可协议EULA),使用ACCEPT_EULA环境变量,并选择适当的 SQL Server 版本,使用MSSQL_PID环境变量。

  3. 我们将使用 Developer 版本,因为我们的应用仅用于开发目的。您可以在图像的文档中阅读有关这些变量使用的更多信息,网址为hub.docker.com/_/microsoft-mssql-server。此外,您需要提供一个MSSQL_SA_PASSWORD环境变量,其中包含实例的 SA 用户密码。

  4. 为此,我们使用了之前创建的mssql Secret 中的值。

  5. 接下来,我们需要将mssql-data PVC 提供的卷挂载到/var/opt/mssql路径。

  6. 这将提供类似于 SQL Server 共享磁盘故障转移实例的故障转移。最后,我们必须确保nodeSelector设置为仅选择 Linux 机器。

现在,继续使用以下步骤进行部署:

  1. 使用kubectl apply -f .\sql-server.yaml命令应用清单文件。

  2. 创建一个sql-server-service.yaml清单文件,为您的 SQL Server 实例创建一个 Kubernetes 服务。根据您的需求,您可以使用ClusterIP类型,或者,如果您将 SQL Server 实例暴露给 Kubernetes 集群外的连接(例如,用于SQL Server Management StudioSSMS)),您可以使用LoadBalancer类型。使用kubectl apply -f .\sql-server-service.yaml命令应用清单文件,如下所示:

kind: Service
apiVersion: v1
metadata:
  namespace: dev
  name: mssql-deployment
spec:
  selector:
    app: mssql
  ports:
    - protocol: TCP
      port: 1433
      targetPort: 1433
  type: LoadBalancer
  1. 您可以使用以下命令观察 Pod 的创建:
PS C:\src> kubectl get pods -n dev --watch
NAME                                READY   STATUS    RESTARTS   AGE
mssql-deployment-58bcb8b89d-7f9xz   1/1     Running   0          8m37s

此时,在dev命名空间中运行着一个 MSSQL Server 2019 实例,可以在集群内部使用mssql-deployment域名系统DNS)名称进行访问。此外,如果您创建了一个 LoadBalancer 服务,可以使用 SSMS 验证实例,提供服务的外部 IP 地址、用户 SA 和您选择的密码。

现在我们可以继续创建投票应用程序的清单文件,并将应用程序部署到集群中。

部署 ASP.NET MVC 应用程序

最后,是大秀的时刻!我们将使用标准的 Kubernetes 部署现在部署我们的投票应用程序,并在下一节中使用 LoadBalancer 服务将其暴露给外部用户。首先,我们需要简要总结我们的应用程序的正确部署所需的内容,如下所示:

  • 将使用packtpubkubernetesonwindows/voting-application:1.0.0 Docker 镜像来部署该应用程序。如果您已将图像推送到自己的图像存储库,则需要相应更改清单文件。我们明确指定1.0.0标签,因为我们希望避免拉取意外的容器图像版本。您可以在文档中阅读有关容器图像的最佳实践的更多信息kubernetes.io/docs/concepts/configuration/overview/#container-images

  • 该应用程序需要设置CONNECTIONSTRING_VotingApplication环境变量,如果需要自定义连接字符串。在我们的部署情况下,连接字符串应具有以下形式:Data Source=mssql-deployment;Initial Catalog=VotingApplication;MultipleActiveResultSets=true;User Id=sa;Password=$(MSSQL_SA_PASSWORD);,其中$(MSSQL_SA_PASSWORD)将从 Kubernetes Secret 中检索。

  • 应用初始数据库迁移是为了填充数据库数据。我们将使用 Kubernetes Job 来执行这个操作——这种方法可以在您的 CI/CD 流水线中进行泛化。迁移本身是使用ef6.exe database update命令执行的——镜像已经包含了这个可执行文件在C:/ef6/目录中。请注意,在生产环境中,您可能希望创建一个单独的 Docker 镜像,专门用于迁移,其中包含所有所需的工具。这样,您可以保持应用程序镜像的干净和尽可能小。

  • 我们暂时不会创建专用的活跃性和就绪性探针,这将在下一章节中进行演示:第十一章,配置应用程序使用 Kubernetes 功能

要部署投票应用程序,请执行以下步骤:

  1. 打开 PowerShell 窗口。

  2. 创建一个名为voting-application.yaml的 Kubernetes 部署清单文件,内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  name: voting-application-frontend
  labels:
    app: voting-application
spec:
  replicas: 5  # (1)
  minReadySeconds: 5  # (2)
  strategy:  # (3)
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 25%
  selector:
    matchLabels:
      app: voting-application
  template:
    metadata:
      name: voting-application-frontend
      labels:
        app: voting-application
    spec:
      nodeSelector:  # (4)
        "beta.kubernetes.io/os": windows
      containers:
      - name: frontend
        image: packtpubkubernetesonwindows/voting-application:1.0.0  # (5)
        env:
        - name: MSSQL_SA_PASSWORD  # (6b)
          valueFrom:
            secretKeyRef:
              name: mssql
              key: SA_PASSWORD  # (6a)
        - name: CONNECTIONSTRING_VotingApplication  # (6c)
          value: "Data Source=mssql-deployment;Initial Catalog=VotingApplication;MultipleActiveResultSets=true;User Id=sa;Password=$(MSSQL_SA_PASSWORD);"
        ports:
          - containerPort: 80
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 500m

让我们解释一下这个清单文件中最重要的部分:

  1. 我们将其定义为一个具有5个初始副本的部署——在我们的情况下,前端应用程序是无状态的,因此我们可以根据需要进行扩展。

  2. 为了防止 IIS 仍在初始化的 Pod 被访问,我们添加了minReadySeconds: 5,以便有一个简单的机制。在下一章中,我们将配置适当的就绪性和活跃性探针。

  3. 我们还明确将部署更新策略设置为RollingUpdate,最大不可用 Pod 数量为25%,允许我们在部署过程中创建多达预期数量的25%的 Pod(这由maxSurge参数控制)。

  4. 接下来,记得设置适当的nodeSelector,只部署到 Windows 节点。

  5. 指定要使用特定标签的镜像——如果使用自己的镜像,相应地更新它。

  6. 为了创建数据库的连接字符串,我们必须首先从mssql Secret (6a)中检索 SA 用户密码,并初始化MSSQL_SA_PASSWORD环境变量(6b),该变量可用于创建存储在CONNECTIONSTRING_VotingApplication变量中的实际连接字符串(6c)。正如所示,您可以使用现有环境变量来初始化新的环境变量:Data Source=mssql-deployment;Initial Catalog=VotingApplication;MultipleActiveResultSets=true;User Id=sa;Password=$(MSSQL_SA_PASSWORD);。当您想要从 Secret 中检索值并用它来定义另一个变量时,这是一种常见的模式。

现在,请按以下步骤继续部署:

  1. 使用kubectl apply -f .\voting-application.yaml命令应用清单文件。等待 Pod 启动,如下所示:
PS C:\src> kubectl get pods -n dev
NAME                                           READY   STATUS    RESTARTS   AGE
mssql-deployment-58bcb8b89d-7f9xz              1/1     Running   0          19h
voting-application-frontend-6876dcc678-kdmcw   1/1     Running   0          19m
voting-application-frontend-6876dcc678-mhdr9   1/1     Running   0          19m
voting-application-frontend-6876dcc678-qsmst   1/1     Running   0          19m
voting-application-frontend-6876dcc678-w5hch   1/1     Running   0          19m
voting-application-frontend-6876dcc678-zqr26   1/1     Running   0          19m

应用程序已成功部署。在访问之前,我们首先需要应用初始数据库迁移——从技术上讲,您可以在不播种数据库的情况下访问应用程序,因为架构将自动初始化,但表中将没有任何数据。要执行数据库迁移,请执行以下步骤:

  1. 为 Kubernetes Job 创建一个名为ef6-update-database.yaml的清单文件,内容如下:
apiVersion: batch/v1
kind: Job
metadata:
  namespace: dev
  name: voting-application-ef6-update-database3
  labels:
    app: voting-application
spec:
  ttlSecondsAfterFinished: 600  # (1)
  template:
    spec:
      nodeSelector:  # (2)
        "beta.kubernetes.io/os": windows
      containers:
      - name: ef6-update-database
        image: packtpubkubernetesonwindows/voting-application:1.0.0  # (3)
        command: ["c:/ef6/ef6.exe",  # (4)
                  "database", "update", 
                  "--verbose",
                  "--assembly", "/inetpub/wwwroot/bin/VotingApplication.dll",
                  "--connection-string", "Data Source=mssql-deployment;Initial Catalog=VotingApplication;MultipleActiveResultSets=true;User Id=sa;Password=$(MSSQL_SA_PASSWORD);",
                  "--connection-provider", "System.Data.SqlClient",
                  "--config", "/inetpub/wwwroot/Web.config"]
        env:
        - name: MSSQL_SA_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mssql
              key: SA_PASSWORD
      restartPolicy: Never
      nodeSelector:
        "beta.kubernetes.io/os": windows
  backoffLimit: 4

关键点在于设置生存时间TTL)秒值,以便触发作业创建的 Pod 的自动清理(1),并确保 Pod 在 Windows 节点上执行(2)。最后一部分是设置容器镜像(3)。在我们的情况下,我们使用与应用程序相同的镜像,因为它包含所有迁移工具。(4)命令是特定于 EF 的,但通常情况下,您必须使用--assembly参数提供包含迁移的.NET 程序集的路径,并使用--connection-string参数提供适当的连接字符串。

  1. 使用kubectl apply -f .\ef6-update-database.yaml命令应用清单文件。

  2. 等待作业运行完成,如下所示:

PS C:\src> kubectl get jobs -n dev
NAME                                      COMPLETIONS   DURATION   AGE
voting-application-ef6-update-database    1/1           50s        103s
  1. 您可以使用标准的kubectl logs命令检查日志,但是您必须提供jobs前缀,如下所示:
PS C:\src> kubectl logs -n dev jobs/voting-application-ef6-update-database
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Target database is: 'VotingApplication' (DataSource: mssql-deployment, Provider: System.Data.SqlClient, Origin: Explicit).
No pending explicit migrations.
Applying automatic migration: 201911201840183_AutomaticMigration.
CREATE TABLE [dbo].[Options] (
...
  1. 现在,如果出现任何问题,例如您无法访问日志(因为 Pod 甚至没有启动)或所有作业执行都以失败结束,最好的调查方法是描述作业对象并找到它创建的 Pod,如下所示:
PS C:\src> kubectl describe job -n dev voting-application-ef6-update-database
...
Events:
 Type    Reason            Age    From            Message
 ----    ------            ----   ----            -------
 Normal  SuccessfulCreate  6m23s  job-controller  Created pod: voting-application-ef6-update-database-chw6s
  1. 使用这些信息,您可以描述任何未正确启动的 Pod,或者甚至可以直接使用作业名称描述它们,如下所示:
kubectl describe pod -n dev voting-application-ef6-update-database

我们的应用程序已经准备就绪 - 即使尚未为其创建 LoadBalancer 服务。为此,我们将使用前几章中描述的技术,如下所示:

  1. 在 PowerShell 窗口中,执行以下命令,将所有网络流量从本地主机端口5000转发到voting-application部署中一个 Pod 的端口80,如下所示:
PS C:\src> kubectl port-forward -n dev deployment/voting-application-frontend 5000:80
Forwarding from 127.0.0.1:5000 -> 80
Forwarding from [::1]:5000 -> 80
  1. 在不关闭 PowerShell 会话的情况下,打开您的网络浏览器并导航到http://localhost:5000。您应该会看到以下屏幕:

恭喜!您已成功部署了投票应用程序 - 现在,我们可以继续使用 LoadBalancer 服务公开部署。

访问应用程序

在本节中,我们将通过创建 LoadBalancer 类型的 Kubernetes 服务,向外部用户公开我们的投票应用程序。服务在第五章中已经进行了深入讨论,Kubernetes 网络。在本节结束时,任何拥有您的新服务的外部 IP 的人都将能够访问该应用程序。

要创建服务,请执行以下步骤:

  1. 打开 PowerShell 窗口。

  2. 为 Kubernetes 服务创建voting-application-service.yaml清单文件,内容如下:

apiVersion: v1
kind: Service
metadata:
  namespace: dev
  name: voting-application-frontend
  labels:
    app: voting-application
spec:
  type: LoadBalancer (1)
  ports:
  - protocol: TCP
    port: 80 (2)
  selector:
    app: voting-application

在这里,关键点是确保服务类型为LoadBalancer (1),并为服务使用正确的端口(2)。在我们的情况下,物理 Azure 负载均衡器上的端口将与应用程序 Pod 的端口相同,因此我们不需要指定targetPort参数。

  1. 使用kubectl apply -f .\voting-application-service.yaml命令应用清单文件。

  2. 等待为新服务提供外部 IP,如下所示:

PS C:\src> kubectl get svc -n dev -w
NAME                          TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)          AGE
mssql-deployment              LoadBalancer   10.0.134.237   104.210.54.75    1433:31446/TCP   21h
voting-application-frontend   LoadBalancer   10.0.50.43     104.42.142.217   80:32357/TCP     62s
  1. 在这种情况下,外部 IP 是104.42.142.217。使用您的网络浏览器并导航到http://104.42.142.217

  2. 您可以尝试多次刷新页面,并从不同的浏览器访问。您将在页面页脚看到您被不同的 Pod 提供服务。如果您在开始时遇到任何延迟,那是因为当第一次访问时,IIS 启动了给定 Pod 中的应用程序池。代码可以在以下片段中看到:

Served by: voting-application-frontend-6876dcc678-zqr26 (10.240.0.44)

现在应用程序对所有外部用户都是可访问的!现在,我们将看看如何扩展应用程序。

扩展应用程序

在我们的设计中,唯一可以扩展的组件是 ASP.NET MVC 前端。SQL Server 无法扩展,因为它在单个节点上以自动故障转移模式运行。真正扩展 SQL Server 需要使用可用性组AG)和专用的 Kubernetes Operator,如前面的部分所述。

在上一章中,我们展示了不同的声明性和命令式方法,介绍了如何扩展部署。现在我们将展示最安全的、声明性的扩展部署的方法。自动扩展将不在此处涵盖,因为它在下一章中有更详细的描述:第十一章,配置应用程序以使用 Kubernetes 功能。要将前端部署从 5 个副本扩展到 10 个,请执行以下步骤:

  1. 打开 PowerShell 窗口。

  2. 修改您现有的voting-application.yaml清单文件,更改副本的数量,如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  ...
spec:
  replicas: 10
  1. 使用kubectl apply -f .\voting-application.yaml命令应用清单文件。

  2. 观察滚动的状态,如下面的代码块所示:

PS C:\src> kubectl get deployments -n dev -w
NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
mssql-deployment              1/1     1            1           21h
voting-application-frontend   6/10    10           5           125m
  1. 最终您会发现它永远不会达到 10 个就绪的副本!发生了什么?答案是我们已经耗尽了两个 Windows 节点的 CPU 预留限制——每个节点都安排了四个 Pod,每个 Pod 预留了500m的 CPU。如果您检查 Standard_D2_v3 Azure VM 的规格,您会发现它有两个 vCPU,这意味着我们已经预留了所有资源。您可以通过检查处于Pending状态的 Pod 来验证这个理论,如下所示:
PS C:\src> kubectl get pods -n dev
NAME                                            READY   STATUS      RESTARTS   AGE
...
voting-application-frontend-6876dcc678-9ssc4    0/1     Pending     0          6m1s
...
  1. 描述处于Pending状态的一个 Pod,如下所示:
PS C:\src> kubectl describe pod -n dev voting-application-frontend-6876dcc678-9ssc4
Events:
 Type     Reason            Age        From               Message
 ----     ------            ----       ----               -------
 Warning  FailedScheduling  <unknown>  default-scheduler  0/5 nodes are available: 2 Insufficient cpu, 3 node(s) didn't match node selector.

在这种情况下,我们可以做什么?考虑以下选项:

一般来说,为了决定要做什么,您必须了解应用程序的要求以及在低 CPU 可用性下的行为。作为演示,我们将执行 CPU 资源的超额分配,如下所示:

  1. 修改voting-application-service.yaml清单文件。

  2. 将请求的 CPU 值更改为250m,保持限制值不变。请注意,我们还需要修改maxUnavailable以允许在部署期间不可用的 Pods 数量更多。使用先前的25%值,我们将遇到死锁情况,因为已经有 10 个 Pod 中的 2 个不可用。此代码如下所示:

apiVersion: apps/v1
kind: Deployment
...
spec:
  strategy:
    ...
    rollingUpdate:
      maxUnavailable: 50%
  ...
  template:
    ...
    spec:
      ...
      containers:
      - name: frontend
        ...
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 250m
  1. 使用kubectl apply -f .\voting-application.yaml命令应用清单文件,并观察部署如何扩展到 10 个副本。

现在您了解了如何扩展我们的投票应用程序,我们可以转到本章的最后一节,展示如何调试应用程序。

调试应用程序

调试应用程序是一个广泛的话题,涉及许多技术,具体取决于需求——可能涉及详细的遥测、跟踪或性能计数器分析。从开发者的角度来看,有一种技术特别重要:与代码调试器一起工作。容器化工作负载的一个问题是,使用诸如 Visual Studio 之类的标准工具进行调试相对较重——进程不在本地运行,您不能像本地进程一样轻松附加调试器。在本节中,我们将展示以下内容:

  • 如何访问日志监视器生成的应用程序日志

  • 如何通过kubectl端口转发启用 Visual Studio 远程调试

访问应用程序日志很简单,因为它涉及标准的kubectl logs命令。在生产场景中,您可能会使用 Azure Log Analytics 或 Elasticsearch 更有效地浏览日志。要访问投票应用程序日志,请执行以下命令,该命令将从部署中的所有 Pod 加载日志:

PS C:\src> kubectl logs -n dev deployment/voting-application-frontend
...
<Source>EventLog</Source><Time>2019-11-20T22:51:17.000Z</Time><LogEntry><Channel>VotingApplication</Channel><Level>Information</Level><EventId>55509</EventId><Message>Using custom connection string provided by environment variable: "data source=mssql-deployment;initial catalog=VotingApplication;multipleactiveresultsets=true;user id=sa;password=*****" </Message></LogEntry>
...

当前的日志设置非常详细,但您可以在 Windows 事件日志中看到 Serilog 记录的所有日志消息,例如前面的行来自VotingApplicationContextFactory类(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter10/01_voting-application-src/Factories/VotingApplicationContextFactory.cs#L28)。

现在,让我们转向更复杂的情景,即通过kubectl端口转发进行 Visual Studio 远程调试。这种用例尚未有文档记录,但它涉及到从非容器化部署中已知的标准技术。我们将执行以下操作:

  1. 为调试创建一个专用的 Docker 镜像,其中安装了 Visual Studio 2019 远程工具(docs.microsoft.com/en-us/visualstudio/debugger/remote-debugging?view=vs-2019)。

  2. 使用特殊标签将镜像推送到注册表。

  3. 修改我们的部署,以使用新的镜像——对于生产情景,您可能更愿意创建一个单独的部署。

  4. 从容器中复制程序数据库PDB)符号文件。我们必须执行此步骤,因为在容器中构建应用程序可能会导致略有不同的输出程序集和符号。

  5. 使用kubectl端口转发功能将远程调试器暴露给本地开发机。

  6. 使用转发的远程调试器将 Visual Studio 附加到w3wp.exe进程。

  7. 加载任何丢失的调试符号。

在我们的情景中,由于我们正在运行经典的.NET Framework,我们受限于传统的 Visual Studio 远程调试器。对于.NET Core,有更多的方法,涉及到 Visual Studio Enterprise 和 Visual Studio Code。您可以在github.com/Microsoft/vssnapshotdebugger-docker上阅读有关在 Linux 上运行的.NET Core 的 Visual Studio Enterprise 快照调试,以及使用 Azure Dev Spaces 的 Visual Studio Code 的更多信息microsoft.github.io/AzureTipsAndTricks/blog/tip228.html

让我们从创建用于调试的修改后的 Dockerfile 开始。

创建一个调试 Dockerfile 并发布一个调试镜像。

为了创建一个调试 Dockerfile,我们将使用我们的原始Dockerfile.production文件并进行轻微修改。 结果文件是Dockerfile.debuggithub.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter10/01_voting-application-src/Dockerfile.debug)。 让我们总结其内容:

  1. Dockerfile 中的构建阶段看起来几乎相同-唯一的区别是我们正在使用调试配置进行构建。 这将确保我们生成了适当的调试程序集,以及 PDB 符号,如下所示:
FROM mcr.microsoft.com/dotnet/framework/sdk:4.8-windowsservercore-ltsc2019 AS build
ARG PUBLISH_PROFILE=DockerPublishProfileDebug.pubxml
ARG BUILD_CONFIG=Debug

WORKDIR /app

COPY *.sln ./
COPY *.csproj ./
COPY *.config ./
RUN nuget restore

COPY . .
RUN msbuild /p:DeployOnBuild=true /p:PublishProfile=$env:PUBLISH_PROFILE /p:Configuration=$env:BUILD_CONFIG
  1. 在最终的构建阶段,我们首先下载并安装 Visual Studio 2019 远程工具。 我们正在公开端口4020,因为我们将使用该端口托管远程调试器,如下面的代码块所示:
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2019 AS runtime

WORKDIR /temp
RUN powershell.exe -Command Invoke-WebRequest https://aka.ms/vs/16/release/RemoteTools.amd64ret.enu.exe -OutFile VS_RemoteTools.exe 
RUN powershell.exe -Command ./VS_RemoteTools.exe /install /quiet
EXPOSE 4020
  1. 图像的其余部分保持不变,除了ENTRYPOINT。 我们修改它,以便远程调试器进程(msvsmon.exe)在后台启动。 原则上,在容器中在后台启动另一个进程并不是一种推荐的做法,但在我们的情况下,我们希望以最快的方式启动远程调试器以及其他服务。 此命令的语法是特定于 Powershell 的,并且可以在以下代码块中看到:
ENTRYPOINT Start-Process -NoNewWindow 'C:\Program Files\Microsoft Visual Studio 16.0\Common7\IDE\Remote Debugger\x64\msvsmon.exe' -ArgumentList /nostatus,/silent,/noauth,/anyuser,/nosecuritywarn,/port,4020; C:\ServiceMonitor.exe w3svc

调试 Dockerfile 准备就绪后,我们可以创建图像并将其推送到 Docker Hub。 请执行以下步骤:

  1. 我们将使用一种约定,即调试图像的标记将在标记中带有-debug后缀-例如,对于生产标记 1.0.0,我们将使用调试标记1.0.0-debug。 另一种选择是为调试图像创建一个新的专用图像存储库。 要构建图像,请在投票应用程序源的根目录中执行以下命令(相应地使用您自己的图像存储库名称):
docker build -t packtpubkubernetesonwindows/voting-application:1.0.0-debug -f .\Dockerfile.debug .
  1. 构建完成后,将新图像推送到 Docker Hub,如下所示:
docker push packtpubkubernetesonwindows/voting-application:1.0.0-debug

图像推送后,我们准备推出应用程序的调试部署。

更新 Kubernetes 部署

如前所述,为了简单起见,我们将重用相同的 Kubernetes 部署和服务以启用调试。 我们需要对原始voting-application.yaml清单文件进行以下修改:

apiVersion: apps/v1
kind: Deployment
metadata:
  ...
spec:
  replicas: 1
  ...
  template:
    ...
    spec:
      ...
      containers:
      - name: frontend
        image: packtpubkubernetesonwindows/voting-application:1.0.0-debug
        imagePullPolicy: Always

将副本的数量修改为1,确保在调试时,我们只有一个 Pod 接收流量。这意味着我们可以轻松地使用调试器在用户界面(UI)中断任何操作。除此之外,我们还需要将镜像更新为我们的新packtpubkubernetesonwindows/voting-application:1.0.0-debug标签,并将imagePullPolicy设置为Always,以便更轻松地引入更改。例如,如果您发现了一个错误并希望快速重新部署镜像并重新连接,您可以使用相同的标签构建镜像,推送它,并手动删除部署中当前运行的 Pod。这将重新创建 Pod,并且由于Always策略,镜像将再次被拉取。

现在,使用kubectl apply -f .\voting-application.yaml命令应用清单文件。我们的设置已经准备好连接 Visual Studio 调试器。

连接 Visual Studio 远程调试器

最后一步是使用远程调试器将您的 Visual Studio 2019 连接到运行在容器内部的 IIS 应用程序池进程。这个过程并不是完全自动化的(但可以脚本化),并且可以进一步统一容器镜像和本地开发机器之间的 PDB 符号。要连接调试器,请执行以下步骤:

  1. 打开 PowerShell 窗口。

  2. 使用以下标准命令确定您的应用程序 Pod 的名称:

kubectl get pods -n dev
  1. 使用kubectl cp命令将VotingApplication.pdb文件复制到当前目录,具体如下:
PS C:\src> kubectl cp -n dev voting-application-frontend-66b95ff674-mmsbk:/inetpub/wwwroot/bin/VotingApplication.pdb VotingApplication.pdb
tar: Removing leading '/' from member names
  1. 或者,您可以在本地使用 Docker 执行此操作,通过创建临时容器并使用以下命令复制文件:
$id = $(docker create packtpubkubernetesonwindows/voting-application:1.0.0-debug)
docker cp $id`:/inetpub/wwwroot/bin/VotingApplication.pdb VotingApplication.pdb
docker rm -v $id
  1. 使用kubectl port-forward命令将所有流量从本地的5000端口转发到 Pod 中的4020端口,这是 Visual Studio 远程调试器暴露的地方,具体如下:
PS C:\src> kubectl port-forward -n dev deployment/voting-application-frontend 5000:4020
Forwarding from 127.0.0.1:5000 -> 4020
Forwarding from [::1]:5000 -> 4020
  1. 现在,您已经准备好将 Visual Studio 2019 连接到远程调试器。在 Visual Studio 中打开VotingApplication.sln,并导航到调试 > 附加到进程...,如下截图所示:

  1. 在对话框中,将连接类型设置为远程(无身份验证),将连接目标设置为转发端口localhost:5000,选择显示所有用户的进程,并单击刷新按钮,如下截图所示:

  1. 您应该看到容器中运行的所有进程列表。如果在这一点上遇到连接问题,您可以在 Kubernetes 中执行进入容器,并检查msvsmon.exe进程是否仍在运行。如果没有,您可以重新创建容器或使用与 Dockerfile 中相同的命令手动启动进程,就像这样:
PS C:\src> kubectl exec -n dev -it voting-application-frontend-66b95ff674-vn256 powershell
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
PS C:\inetpub\wwwroot> Get-Process
...
 218      12     2240       9016       0.06  12360   2 msvsmon
  1. 现在,在浏览器中导航到服务的外部 IP。我们需要确保 IIS 应用程序池进程(w3wp.exe)已启动。

  2. 在“附加到进程”对话框中,刷新进程列表,找到w3wp.exe进程,并单击“附加”按钮,如下截图所示:

  1. 调试器已附加,但可能缺少符号。您可以通过在代码的任何位置设置断点来验证这一点,如下截图所示:

  1. 如果是这种情况,要加载自定义 PDB 符号,请导航到调试 > 窗口 > 模块,找到VotingApplication.dll程序集,右键单击,然后选择“加载符号”,如下截图所示:

  1. 导航到您复制了VotingApplication.pdb文件的目录。符号将自动加载,断点将变得可触发。

  2. 在 Web 浏览器中,执行应该触发断点的操作。

  3. 现在,根据您的连接速度,Visual Studio 可能会在调试器完全附加之前冻结一段时间(甚至几分钟)。但是,经过这一最初的小问题后,调试体验应该是令人满意的,如下截图所示:

恭喜——您已成功远程附加调试器到运行在 Kubernetes Pod 内的进程!

总结

在本章中,您已经学会了如何将 ASP.NET MVC 应用程序部署到 Kubernetes 集群,以及如何将容器化的 Microsoft SQL Server 2019 部署到其中。我们展示了如何使现有的.NET Framework 应用程序适合云环境,以及如何为这些应用程序创建健壮的 Dockerfile。接下来,我们为我们的投票应用程序准备了一个 AKS Engine 集群部署,并以单节点故障转移模式部署了由 Azure Disk 支持的 Microsoft SQL Server 2019。该应用程序是使用 Kubernetes 部署部署到集群中的,并且我们使用了 Kubernetes Job 来应用 EF 数据库迁移。之后,您将了解有关扩展 Kubernetes 部署和计算资源超额分配的更多信息。最后,您将深入了解如何通过kubectl端口转发使用 Visual Studio 2019 的远程调试器来调试在 Kubernetes Pod 中运行的.NET Framework 应用程序。

下一章将重点介绍更高级的 Kubernetes 功能-我们将扩展我们的投票应用程序,充分利用 Kubernetes 的功能。

问题

  1. 在 Kubernetes Pod 中运行应用程序注入配置的可能方式有哪些?

  2. 微软提供的 Windows 容器日志监视器的目的是什么?

  3. 为什么对于复制的应用程序迁移数据库架构是一项具有挑战性的任务?

  4. 为什么我们要使用由 Azure Disk 支持的持久卷来存储 Microsoft SQL Server 数据?

  5. 如何将 EF 数据库迁移应用到在 Kubernetes 中运行的应用程序?

  6. Kubernetes 中的 CPU/内存资源超额分配是什么?

  7. 为什么您需要使用kubectl端口转发才能连接到容器中的 Visual Studio 远程调试器?

您可以在本书的评估部分找到这些问题的答案。

进一步阅读

第十一章:配置应用程序以使用 Kubernetes 功能

上一章演示了如何在 Kubernetes 中处理容器化的 Windows 应用程序-现在,我们将扩展我们的投票应用程序,以使用更先进的功能,使编排更加健壮和自动化。多年来,Kubernetes 已经扩展了越来越多的功能,从细粒度的基于角色的访问控制(RBAC)或 Secrets 管理到使用水平 Pod 自动缩放器(HPA)进行自动缩放,这是容器编排的圣杯。当然,我们无法在本书的范围内涵盖所有这些功能,但我们将包括一些最有用的功能,以帮助运行容器化的 Windows 应用程序。另外,请记住,当您运行本地 Kubernetes 集群时,一些功能是不可用的,例如特定于云的 StorageClass 提供程序-我们将要呈现的所有示例都假定您正在运行 AKS Engine Kubernetes 集群。

在本章中,我们将涵盖以下主题:

  • 使用命名空间隔离应用程序

  • 使用活动探针和就绪探针进行健康监控

  • 指定资源限制和配置自动缩放

  • 使用 ConfigMaps 和 Secrets 管理应用程序配置

  • 在 Windows 节点上管理持久数据存储

  • 为部署配置滚动更新

  • RBAC

技术要求

在本章中,您将需要以下内容:

  • 安装了 Windows 10 Pro、企业版或教育版(1903 版或更高版本,64 位)

  • Microsoft Visual Studio 2019 Community(或任何其他版本),如果您想编辑应用程序的源代码并对其进行调试-Visual Studio Code 对经典.NET Framework 的支持有限

  • 一个 Azure 帐户

  • 使用 AKS Engine 部署的 Windows/Linux Kubernetes 集群,准备部署上一章的投票应用程序

要跟着做,您需要自己的 Azure 帐户来为 Kubernetes 集群创建 Azure 资源。如果您之前还没有为前几章创建帐户,您可以阅读有关如何获取个人使用的有限免费帐户的更多信息azure.microsoft.com/en-us/free/

使用 AKS Engine 部署 Kubernetes 集群已在第八章中进行了介绍,部署混合 Azure Kubernetes 服务引擎集群。将投票应用程序部署到 Kubernetes 已在第十章中进行了介绍,部署 Microsoft SQL Server 2019 和 ASP.NET MVC 应用程序

您可以从官方 GitHub 存储库下载本章的最新代码示例,网址为github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter11

使用命名空间隔离应用程序

在上一章中,我们已经使用了一个命名空间(名为dev)来将应用程序的组件逻辑地分组到现有物理 Kubernetes 集群中的虚拟集群中。命名空间的一般原则是提供资源配额和对象名称的范围——给定命名空间内的名称必须是唯一的,但它们不必在不同的命名空间中是唯一的。默认情况下,Kubernetes 提供以下开箱即用的命名空间:

  • kube-system:由 Kubernetes 系统创建的对象的命名空间,例如kube-apiserverkube-proxy Pods。

  • kube-public:一个可以被所有用户阅读的命名空间,也不需要经过身份验证——它将在由 kubeadm 引导的集群中创建,并且通常用于系统使用。

  • default:没有其他命名空间的对象的命名空间。

根据您的需求和团队的规模,您可能更愿意仅使用对象标签(小团队)或在命名空间级别分隔对象(大团队):

  • 对于小团队,其中单个开发人员能够理解整个系统(大约 10 个微服务),并且整个开发环境可以使用本地集群(如在 VM 上运行的 minikube 或 kubeadm 部署)进行托管,可以仅使用默认命名空间来部署生产服务。或者,您可以为生产工作负载使用专用命名空间,并为开发/分段环境使用单独的命名空间。

  • 对于快速增长的中等规模团队,在这种团队中,单个开发人员不在整个系统范围内工作,为每个子团队提供专用的命名空间可能更容易,特别是如果在本地 Kubernetes 集群上无法创建整个开发环境。

  • 对于大型团队,子团队几乎独立运作,为每个团队单独创建生产和开发命名空间可能是一个好主意。您还可以考虑为每个命名空间使用资源配额和使用 RBAC。

  • 对于企业组织来说,个别团队甚至可能不知道其他团队的存在,创建单独的集群可能比使用命名空间来划分单个集群更容易。这样可以更轻松地管理资源和计费,并在出现问题时提供更好的部署边界。

在创建服务对象时,命名空间会影响服务的完全限定域名FQDN)。FQDN 的形式为<service-name>.<namespace-name>.svc.cluster.local—这意味着如果您在 Pod 中调用服务时使用<service-name>,调用将被限定在此 Pod 所在的命名空间。请注意,跨命名空间调用服务是可能的,但您需要指定 FQDN。

让我们演示如何为您的对象创建一个命名空间。

创建命名空间

要创建一个名为prod的命名空间,您可以使用以下命令:

kubectl create namespace prod

与其他对象一样,通常建议使用声明性对象配置管理,并将清单文件应用到 Kubernetes 集群。以下的namespace-prod.yaml清单文件将创建prod命名空间,另外指定了ResourceQuota对象,用于确定此命名空间的总 CPU 和内存配额:

---
kind: Namespace
apiVersion: v1
metadata:
  name: prod
  labels:
    name: prod
---
apiVersion: v1
kind: ResourceQuota
metadata:
  namespace: prod
  name: default-resource-quota
spec:
  hard:
    requests.cpu: 500m
    requests.memory: 1Gi
    limits.cpu: "1"
    limits.memory: 2Gi

要应用清单文件,请执行以下命令:

kubectl apply -f .\namespace-prod.yaml

然后,您可以使用kubectl describe命令来检查我们的命名空间中使用了多少资源。

PS C:\src> kubectl describe resourcequota -n prod
Name:            default-resource-quota
Namespace:       prod
Resource         Used  Hard
--------         ----  ----
limits.cpu       0     1
limits.memory    0     2Gi
requests.cpu     0     500m
requests.memory  0     1Gi

Kubernetes 中的资源配额是高度可定制的,可以应用于不同的资源,并使用复杂的选择器进行范围限定。您可以在官方文档中了解更多信息:kubernetes.io/docs/concepts/policy/resource-quotas/

现在,您已经知道如何管理命名空间,让我们看看如何使用kubectl命令有效地使用它们。

kubectl 命令和命名空间

kubectl命令按照惯例操作命名空间范围的对象,使用--namespace-n标志来指定应用于命令的命名空间。如果您需要查询所有命名空间中的对象,可以使用--all-namespaces标志。例如,要列出prod命名空间中的所有 Pods,请使用以下命令:

kubectl get pods -n prod

在之前的章节中,您经常使用了这个构造。但是,值得知道的是,如果命令没有提供命名空间,它将使用当前 kubeconfig 上下文中设置为默认的命名空间。换句话说,它不一定是默认的命名空间 - 这完全取决于您的上下文设置。我们在第六章中深入讨论了上下文,与 Kubernetes 集群交互 - 为了完整起见,我们将展示如何更改当前上下文中使用的命名空间。要在当前上下文中永久设置prod命名空间,请使用以下命令:

kubectl config set-context --current --namespace=prod

现在,任何支持指定命名空间的命令将默认使用prod命名空间。

删除命名空间

与其他对象类似,建议以命令方式删除命名空间。要删除prod命名空间,请执行以下命令:

kubectl delete namespace prod

请注意,此命令将删除此命名空间中的所有对象,这意味着这是一个极具破坏性的命令,应谨慎使用!

在下一节中,我们将看到如何使用探针配置容器监视活动性和就绪性。

使用活动性和就绪性探针进行健康监控

在 Kubernetes 中,探针由 kubelet 用于确定 Pod 的状态 - 您可以使用它们来自定义如何检查 Pod 是否准备好为您的流量提供服务,或者容器是否需要重新启动。您可以为在 Pod 中运行的每个容器配置三种类型的探针:

  • 就绪探针:用于确定给定容器是否准备好接受流量。只有当 Pod 的所有容器都准备就绪时,Pod 才被视为准备就绪。不准备就绪的 Pod 将从服务端点中删除,直到它们再次准备就绪为止。

  • 活动性探针:用于检测容器是否需要重新启动。这可以帮助解决容器陷入死锁或其他问题的情况,当容器进程处于活动状态但无法正常运行时。重新启动容器可能会增加该情况下 Pod 的可用性。

  • 启动探针:这是用于确定容器是否已完全启动的附加探针-在此探针成功返回之前,就绪和存活探针都是禁用的。这对于由于某些初始化而具有长启动时间的容器特别有用。通过这种方式,您可以避免存活探针的过早终止。

默认情况下,Pod 容器上没有配置探针。但是,只有在 Pod 容器已启动(在 Docker 意义上)并且重新启动容器(当然取决于您的重新启动策略)后,Kubernetes 才会提供流量。

所有类型的探针都可以使用三种类型的处理程序操作进行配置:

  • 运行命令(exec)-如果容器中运行的给定命令返回非零退出代码,则探针处于失败状态。

  • 执行 HTTP GET 请求(httpGet)-只有当容器对 HTTP GET 请求做出大于或等于 200 且小于 400 的 HTTP 代码响应时,探针才处于成功状态。

  • 在指定端口向容器打开 TCP 套接字(tcpSocket)-如果可以建立连接,则探针处于成功状态。

您还应考虑使用终止优雅期限来正确管理 Pod 的容器化应用程序生命周期,并在接收到 SIGTERM 信号时使应用程序优雅地退出(cloud.google.com/blog/products/gcp/kubernetes-best-practices-terminating-with-grace)。请注意,对于 Windows Pod,截止优雅期限在 Kubernetes 1.17 版本中不受支持。

在处理具有许多依赖组件的大型分布式系统时,使用探针时存在一些注意事项和最佳实践。我们将在解释每种类型的探针时详细介绍细节-反映示例的投票应用程序源代码可以在官方 GitHub 存储库中找到,网址为github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter11/02_voting-application-probes-src。首先,让我们看一下最受欢迎的探针,即就绪探针。

就绪探针

在 Kubernetes 中使用就绪探针来确定 Pod 容器是否准备好接受来自 Kubernetes 服务的流量——不准备好的 Pod(只有所有容器都被认为准备好的 Pod 才算准备好)将从服务端点列表中删除,直到它们再次准备好。换句话说,这是一个通知给定 Pod 可以用于服务请求的信号。

就就绪探针而言,有一些已经建立的最佳实践是您应该考虑的:

  • 只要您的容器可能无法在容器启动后立即准备好为流量提供适当的服务,就使用此探针。

  • 确保在就绪探针评估期间检查缓存预热或数据库迁移状态。您还可以考虑在尚未启动的情况下启动预热的实际过程,但要谨慎使用——就绪探针将在 Pod 的生命周期中不断执行,这意味着您不应该为每个请求执行任何昂贵的操作。或者,您可能希望为此目的使用在 Kubernetes 1.16 中新引入的启动探针。

  • 对于暴露 HTTP 端点的微服务应用程序,考虑始终配置 httpGet 就绪探针。这将确保在容器成功运行但 HTTP 服务器尚未完全初始化时,所有情况都得到覆盖。

  • 在应用程序中为就绪检查使用一个单独的专用 HTTP 端点是一个好主意,例如,一个常见的约定是使用 /health

  • 如果您在此类探针中检查依赖项(外部数据库和日志记录服务)的状态,请注意共享依赖项,例如投票应用程序中的 SQL Server。在这种情况下,您应该考虑使用探针超时,该超时大于外部依赖项的最大允许超时时间,否则可能会出现级联故障,可用性降低,而不是偶尔增加的延迟。

对于使用 IIS(Internet Information Services 的缩写)托管的 Web 应用程序,就绪探针非常有意义——IIS 应用程序池需要完全启动,数据库迁移可能尚未应用。例如,我们将为我们的投票应用程序配置一个简单的就绪探针,如下所示:

  • ASP.NET MVC 应用程序将实现一个专用控制器,用于提供 /health 请求。

  • 将检查未决的数据库迁移。请注意,这将间接验证数据库连接状态,这在某些情况下可能是不可取的。因此,我们将使用大于 30 秒的探针超时(默认的 SQL 命令超时)。

  • 控制器操作将返回一个简单的 JSON。在检查失败的情况下,HTTP 状态将为 503,在成功的情况下为 200。

要为投票应用程序添加就绪探针,请按照以下步骤进行:

  1. 健康检查控制器操作的实现可以在HealthController类中找到(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter11/02_voting-application-probes-src/Controllers/HealthController.cs),如下所示:
public ActionResult CheckHealth()
{
    this.Response.TrySkipIisCustomErrors = true;

    if (!this.db.Database.CompatibleWithModel(throwIfNoMetadata: true))
    {
        this.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
        return this.Json(new { status = "Database migrations pending" }, JsonRequestBehavior.AllowGet);
    }

    return this.Json(new { status = "Ok" }, JsonRequestBehavior.AllowGet);
}
  1. 另外,您需要记住在RouteConfig类(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter11/02_voting-application-probes-src/App_Start/RouteConfig.cs)中修改应用程序的路由配置,然后是默认路由映射。
routes.MapRoute(
    name: "Health",
    url: "health",
    defaults: new { controller = "Health", action = "CheckHealth" });
  1. 与上一章一样,构建应用程序的 Docker 镜像,将其标记为 1.1.0 版本,并将其推送到 Docker Hub。在我们的演示案例中,我们将使用packtpubkubernetesonwindows/voting-application:1.1.0镜像。

  2. 修改部署清单文件voting-application.yaml,以包括frontend容器的以下就绪探针配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  name: voting-application-frontend
  ...
spec:
  ...
  template:
    ...
    spec:
      ...
      containers:
      - name: frontend
        image: packtpubkubernetesonwindows/voting-application:1.1.0
        ...
        readinessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 40
          successThreshold: 1
          failureThreshold: 3
        ...

探针被配置为调用/health端点,这将执行我们之前实现的控制器操作。探针配置中的重要部分如下:

    • initialDelaySeconds设置为30秒,以允许 IIS 完全初始化。原来,对在ServiceMonitor.exe监督下运行的应用程序进行过早调用可能会导致容器过早退出(也许是ServiceMonitor.exe实现中的一个错误)。
  • timeoutSeconds设置为40秒,以超过默认设置为30秒的 SQL Server 数据库超时。

  1. 现在,使用kubectl apply -f .\voting-application-readiness-probe.yaml命令应用清单文件。

  2. 像往常一样,使用kubectl get pods -n devkubectl describe命令来检查部署过程。在 Pod 事件中,你可以验证 Pod 是否有任何就绪失败。

  3. 在 Web 浏览器中,当你导航到应用程序时,你不应该遇到任何 IIS 应用程序池启动延迟——Web 服务器将通过就绪检查进行预热。

现在,让我们来看看另一个确定 Pod 容器存活状态的探针。

存活探针

第二种探针是存活探针,它可以在清单中类似于就绪探针进行配置。存活探针用于确定是否需要重新启动 Pod 容器。当进程尚未退出但无法处理任何操作时,这种类型的探针可能对恢复死锁或其他类型的容器问题有用。

与就绪探针类似,关于何时以及如何使用存活探针,有一些指导方针。

  • 存活探针应该谨慎使用。错误的配置可能导致服务和容器重启循环中的级联故障。作为一个快速实验,你可以重新部署投票应用程序清单,其中用存活探针替换就绪探针,配置类似但超短的超时和延迟——你将遇到多次随机崩溃和应用程序的可用性不佳!

  • 除非你有充分的理由,否则不要使用活跃探针。一个充分的理由可能是你的应用程序中存在一个已知的死锁问题,但尚未找到根本原因。

  • 执行简单快速的检查来确定进程的状态,而不是它的依赖关系。换句话说,在存活探针中不要检查外部依赖的状态——这可能会导致由于大量容器重启而产生级联故障,并且会过载一小部分服务 Pod。

  • 如果你的容器中运行的进程能够在遇到无法恢复的错误时崩溃或退出,那么你可能根本不需要存活探针。

  • 使用保守的initialDelaySeconds设置,以避免任何过早的容器重启并陷入重启循环。

如果您不确定ServiceMonitor.exeLogMonitor.exe入口进程的内部情况,那么由 IIS 托管的 Web 应用程序可能是使用活动探针的一个很好的选择。理论上,它们应该在 IIS 或 IIS 应用程序池出现问题时使容器崩溃,但让我们假设我们需要自己实现这些检查。我们将实现一个活动探针,它将使用exec处理程序检查 IIS 应用程序池是否正在运行。为此,请按照以下步骤进行操作:

  1. 使用Deployment为我们的应用程序修改voting-application.yaml清单文件。为frontend容器添加以下活动探针配置:
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  name: voting-application-frontend
  ...
spec:
  ...
  template:
    ...
    spec:
    ...
    containers:
    - name: frontend
    image: packtpubkubernetesonwindows/voting-application:1.1.0
    ...
    livenessProbe:
      exec:
        command:
        - powershell.exe
        - -Command
        - if ((Get-WebAppPoolState DefaultAppPool).Value -ne "Started") { throw "Default IIS App Pool is NOT started" }
        initialDelaySeconds: 45
        periodSeconds: 10
        timeoutSeconds: 10
        successThreshold: 1
        failureThreshold: 3
        ...

探针被配置为执行 PowerShell 命令,if ((Get-WebAppPoolState DefaultAppPool).Value -ne "Started") { throw "Default IIS App Pool is NOT started" },该命令检查默认的 IIS 应用程序池是否处于Started状态。如果不是,则将抛出异常,并且 PowerShell 进程将以非零退出代码退出,导致探针进入失败状态。

  1. 现在,使用kubectl apply -f .\voting-application-readiness-probe.yaml命令应用清单文件。

  2. 再次使用kubectl get pods -n devkubectl describe命令检查滚动升级过程。在 Pod 事件中,您可以验证 Pod 是否有任何活动失败。

在使用exec处理程序时,您应该仔细分析所选命令的行为。据报道,exec处理程序在某些情况下会导致僵尸进程膨胀。

最后,让我们快速看一下最后一种类型的探针,即启动探针。

启动探针

最近在 Kubernetes 1.16 中引入了启动探针,以支持容器可能需要比设置在就绪探针中的initialDelaySeconds + failureThreshold * periodSeconds更多时间进行初始化的情况。通常情况下,您应该为启动探针使用与就绪探针相同的处理程序配置,但使用更长的延迟。如果容器在initialDelaySeconds + failureThreshold * periodSeconds内未准备好进行就绪探针,则容器将被终止,并受到 Pod 的重启策略的影响。

我们的投票应用程序不需要专门的启动探针,但在部署清单文件中的示例定义可能如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  name: voting-application-frontend
  ...
spec:
  ...
  template:
    ...
    spec:
      ...
      containers:
      - name: frontend
        image: packtpubkubernetesonwindows/voting-application:1.1.0
        ...
        startupProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 60
          timeoutSeconds: 40
          successThreshold: 1
          failureThreshold: 5
        ...

在下一节中,我们将专注于为 Pod 分配资源限制以及如何为我们的投票应用程序配置自动缩放。

指定资源限制和配置自动缩放

作为容器编排器,Kubernetes 默认提供了两个重要功能,帮助管理您的集群资源:

  • Pod 容器的资源请求和限制

  • HPA,它允许根据 CPU 资源使用情况(稳定支持)、内存资源使用情况(beta 支持)或自定义指标(也是 beta 支持)自动扩展您的部署或有状态集

让我们首先看一下指定资源请求和限制。

资源请求和限制

当您创建一个 Pod 时,可以指定其容器需要多少计算资源 - 我们已经在上一章中对投票应用程序分配资源进行了简短的练习。一般来说,计算资源是 CPU 和 RAM 内存 - Kubernetes 还能够管理其他资源,例如 Linux 上的 HugePages 或本地节点上的临时存储。

Kubernetes 资源模型提供了两类资源之间的额外区分:可压缩和不可压缩。简而言之,可压缩资源可以轻松进行限流,而不会造成严重后果。这样的资源的一个完美例子是 CPU - 如果您需要限制给定容器的 CPU 使用率,容器将正常运行,只是速度较慢。另一方面,我们有不可压缩资源,如果不加限制会造成严重后果 - 内存分配就是这样一个资源的例子。

有两份很棒的设计提案文件描述了 Kubernetes 资源模型(github.com/kubernetes/community/blob/master/contributors/design-proposals/scheduling/resources.md)和资源服务质量(github.com/kubernetes/community/blob/master/contributors/design-proposals/node/resource-qos.md)。我们强烈建议阅读它们,以充分了解 Kubernetes 资源管理的愿景以及已经实现的功能。

您可以为 Pod 容器指定两个值,关于资源分配:

  • requests:这指定了系统提供的特定资源的保证数量。你也可以反过来想,这是 Pod 容器从系统中需要的特定资源的数量,以便正常运行。Pod 的调度取决于requests值(而不是limits)。

  • limits:这指定了系统提供的特定资源的最大数量。如果与requests一起指定,这个值必须大于或等于requests。根据资源是可压缩还是不可压缩,超出限制会产生不同的后果——可压缩资源(CPU)将被限制,而不可压缩资源(内存)可能会导致容器被杀死。

使用不同的requestslimits值允许资源超额分配,这对于有效处理资源使用的短暂突发情况并在平均情况下更好地利用资源是有用的。如果根本不指定限制,容器可以在节点上消耗任意数量的资源。这可以通过命名空间资源配额(本章前面介绍的)和限制范围来控制——你可以在文档中阅读更多关于这些对象的信息kubernetes.io/docs/concepts/policy/limit-range/

我们在 Kubernetes 中的 Windows 节点上涵盖了资源管理支持的详细信息,详见第四章,Kubernetes 概念和 Windows 支持。重要的是,Windows 目前缺乏对内存杀手的支持(Kubernetes 中即将推出的 Hyper-V 容器功能可能会提供一些内存限制的支持)。这意味着超出 Windows 容器内存的limits值不会导致任何限制或容器重启。在这里,经验法则是仔细使用requests来管理内存调度,并监视任何突然的内存分页。

在深入配置细节之前,我们需要了解 Kubernetes 中用于测量 CPU 资源和内存的单位是什么。对于 CPU 资源,基本单位是Kubernetes CPUKCU),其中1等同于例如 Azure 上的 1 个 vCPU,GCP 上的 1 个 Core,或者裸机上的 1 个超线程核心。允许使用小数值:0.1也可以指定为100m(毫 CPU)。对于内存,基本单位是字节;当然,您可以指定标准单位前缀,如MMiGGi

为了演示如何使用资源limitsrequests,请按照以下步骤操作:

  1. 修改voting-application.yaml部署配置,使其不指定任何更新strategy,并为 CPU 和内存设置资源分配:
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  name: voting-application-frontend
  ...
spec:
  replicas: 5
  ...
  # strategy:
  ...
  template:
    ...
    spec:
      ...
      containers:
      - name: frontend
        ...        
        resources:
          limits:
            cpu: 1000m
          requests:
            cpu: 1000m
            memory: 256Mi

对于内存,我们遵循 Windows 节点的当前建议——我们只指定了想要请求多少内存。为了模拟资源耗尽,我们指定了一个大的请求值,将消耗 Windows 节点的所有集群 CPU。这是因为两个具有 Azure VM 类型 Standard_D2_v3 的节点每个都有两个 vCPU,并且运行五个副本,我们总共需要五个 vCPU。需要删除更新strategy以避免在部署过程中出现任何死锁。

  1. 使用kubectl apply -f .\voting-application.yaml命令应用配置文件。

  2. 现在,仔细观察您的部署中新 Pod 的创建。您会注意到有一些 Pod 显示Pending状态:

PS C:\src> kubectl get pods -n dev
NAME                                            READY   STATUS      RESTARTS   AGE
voting-application-frontend-54bbbbd655-nzt2n    1/1     Running     0          118s
voting-application-frontend-54bbbbd655-phdhr    0/1     Pending     0          118s
voting-application-frontend-54bbbbd655-qggc2    1/1     Running     0          118s
...
  1. 这是预期的,因为voting-application-frontend-54bbbbd655-phdhr Pod 无法被调度到任何节点,因为没有可用的 CPU 资源。要检查实际原因,描述 Pod 并检查 Events
PS C:\src> kubectl describe pod -n dev voting-application-frontend-54bbbbd655-phdhr
Events:
 Type     Reason            Age        From                 Message
 ----     ------            ----       ----                 -------
 Warning  FailedScheduling  <unknown>  default-scheduler    0/5 nodes are available: 2 Insufficient cpu, 3 node(s) didn't match node selector.
  1. 正如预期的那样,由于所有匹配节点选择器的节点上都没有足够的 CPU 资源,Pod 无法被调度。让我们通过降低 Pod 容器的 requestslimits CPU 值来解决这个问题——修改 voting-application.yaml 配置文件,使 requests 设置为 250mlimits 设置为 500m

  2. 使用kubectl apply -f .\voting-application.yaml命令应用配置文件,并观察成功的部署。

现在您知道如何为您的容器分配和管理资源,我们可以演示如何使用 HPA 对您的应用程序进行自动缩放。

HPA

Kubernetes 的真正力量在于 HPA 实现的自动扩展,它是由HorizontalPodAutoscaler API 对象支持的专用控制器。在高层次上,HPA 的目标是根据当前 CPU 利用率或其他自定义指标(包括同时使用多个指标)自动扩展部署或 StatefulSet 中副本的数量。根据指标值确定目标副本数量的算法的详细信息可以在kubernetes.io/docs/tasks/run-application/horizontal-Pod-autoscale/#algorithm-details找到。HPA 是高度可配置的,在本书中,我们将介绍基于目标 CPU 使用率自动扩展的标准场景。

我们的投票应用程序公开了不需要太多 CPU 的功能,这意味着可能很难按需触发自动扩展。为了解决这个问题,我们将添加一个专用的控制器动作,可以模拟具有给定目标百分比值的恒定 CPU 负载。用于压力模拟的packtpubkubernetesonwindows/voting-application:1.2.0 Docker 镜像的源代码可以在github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter11/08_voting-application-hpa-src找到。如果您想自定义应用程序,请在 Visual Studio 2019 中打开您的解决方案,并按照以下步骤操作:

  1. 定义StressCpuWorker类(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter11/08_voting-application-hpa-src/Services/CpuStressWorker.cs),其中包含用于模拟 CPU 压力的主要工作代码:
private void StartCpuStress()
{
    this.logger.Info($"Environment.ProcessorCount: {Environment.ProcessorCount}");

    for (int i = 0; i < Environment.ProcessorCount; i++)
    {
        var thread = new Thread(
            () =>
                {
                    var watch = new Stopwatch();
                    watch.Start();

                    while (this.isEnabled)
                    {
                        if (watch.ElapsedMilliseconds <= this.targetCpuLoad)
                        {
                            continue;
                        }

                        Thread.Sleep(100 - this.targetCpuLoad);

                        watch.Reset();
                        watch.Start();
                    }
                });

        thread.Start();
    }
}

此代码将启动多个线程,数量将等于环境中当前可用的处理器数量,然后通过几乎空的while循环来对每个逻辑处理器进行this.targetCpuLoad毫秒的压力测试。在剩余的 100 毫秒“段”中,线程将进入睡眠状态——这意味着平均而言,我们应该将所有可用的 CPU 负载到this.targetCpuLoad百分比。当然,这取决于分配给容器的处理器数量——这个数字可能会根据您的requestslimits值而变化;您可以随时检查 Pod 日志,以查看此 Pod 可用的逻辑处理器数量。另请注意,即使容器有两个逻辑处理器可用,也不意味着容器能够充分利用它们;负载可能会受到limits值的限制。

  1. HomeController类中(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter11/08_voting-application-hpa-src/Controllers/HomeController.cs),添加一个新的控制器操作,可以通过/Home/StressCpu?value={targetPercent}路由访问。请注意,我们允许通过 GET 请求(而不是 PUT)执行此操作,以便在使用 Web 浏览器时交互更加简单。此外,将IStressCpuWorker注入到构造函数中——最终操作实现如下:
public ActionResult StressCpu([FromUri] int value)
{
    this.Response.StatusCode = (int)HttpStatusCode.Accepted;
    var host = Dns.GetHostEntry(string.Empty).HostName;

    if (value < 0)
    {
        this.cpuStressWorker.Disable();
        return this.Json(new { host, status = $"Stressing CPU turned off" }, JsonRequestBehavior.AllowGet);
    }

    if (value > 100)
    {
        value = 100;
    }

    this.cpuStressWorker.Enable(value);
    return this.Json(new { host, status = $"Stressing CPU at {value}% level" }, JsonRequestBehavior.AllowGet);
}

如果提供正值,此实现将启用 CPU 压力测试;如果提供负值,将禁用压力测试。

  1. NinjectWebCommon类中配置依赖注入(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter11/08_voting-application-hpa-src/App_Start/NinjectWebCommon.cs)。确保StressCpuWorker类被解析为单例:
kernel.Bind<ICpuStressWorker>().To<CpuStressWorker>().InSingletonScope();
  1. 使用标签1.2.0构建 Docker 镜像,并将其推送到您的存储库,就像我们之前做的那样。

准备好镜像后,我们可以继续部署投票应用的新版本并配置自动缩放。为此,请执行以下步骤:

  1. 修改voting-application.yaml清单文件,并确保您使用图像的1.2.0标记,并且resources指定如下:
resources:
  limits:
    cpu: 500m
  requests:
    cpu: 400m
    memory: 256Mi
  1. 在 PowerShell 窗口中,使用kubectl apply -f .\voting-application.yaml命令应用清单文件。

  2. 等待部署完成,并使用此命令观察 Pod 的 CPU 使用情况:

PS C:\src> kubectl top pod -n dev
NAME                                           CPU(cores)   MEMORY(bytes)
mssql-deployment-58bcb8b89d-7f9xz              339m         903Mi
voting-application-frontend-6b6c9557f8-5wwln   117m         150Mi
voting-application-frontend-6b6c9557f8-f787m   221m         148Mi
voting-application-frontend-6b6c9557f8-rjwmj   144m         164Mi
voting-application-frontend-6b6c9557f8-txwl2   120m         191Mi
voting-application-frontend-6b6c9557f8-vw5r9   160m         151Mi

当 IIS 应用程序池完全初始化时,每个 Pod 的 CPU 使用率应稳定在150m左右。

  1. 为 HPA 创建hpa.yaml清单文件:
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  namespace: dev
  name: voting-application-frontend
spec:
  minReplicas: 1
  maxReplicas: 8
  targetCPUUtilizationPercentage: 60
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: voting-application-frontend

此 HPA 将自动将voting-application-frontend部署扩展到18个副本之间,尝试将 CPU 使用率定位到60%。请注意,此目标使用率较高,在生产环境中,您应考虑使用更低、更合适的值。此清单文件与使用kubectl autoscale deployment/voting-application-frontend -n dev --cpu-percent=60 --min=1 --max=8命令创建的 HPA 大致相同。

  1. 使用kubectl apply -f .\hpa.yaml命令应用清单文件。

  2. HPA 受到延迟的影响,以避免频繁波动(即副本计数频繁波动)。默认延迟为五分钟。这意味着在应用后,您应该期望一些延迟,直到 HPA 扩展部署。使用kubectl describe命令监视 HPA 的状态:

PS C:\src> kubectl describe hpa -n dev voting-application-frontend
...
Metrics:                                               ( current / target )
 resource cpu on pods (as a percentage of request): 37% (150m) / 60%
Events:
 Type     Reason                        Age   From                       Message
 ----     ------                        ----  ----                       -------
...
 Normal   SuccessfulRescale             8m6s  horizontal-Pod-autoscaler  New size: 4; reason: All metrics below target
 Normal   SuccessfulRescale             3m3s  horizontal-Pod-autoscaler  New size: 3; reason: All metrics below targetcpu

随着时间的推移,您会注意到 HPA 倾向于缩减到单个副本,因为 CPU 负载不足。

  1. 让我们使用我们的专用端点增加 CPU 负载。在 Web 浏览器中,转到以下 URL:http://<serviceExternalIp>/Home/StressCpu?value=90。这将开始以 90%的目标水平压力 CPU-请记住,根据 Pod 分配的逻辑处理器的方式,实际使用情况可能会有所不同。

  2. 您可以执行多个请求,以确保部署中的更多 Pod 开始对 CPU 施加压力。

  3. 过一段时间,观察 HPA 事件中发生了什么:

 Normal   SuccessfulRescale             7m44s            horizontal-Pod-autoscaler  New size: 4; reason: cpu resource utilization (percentage of request) above target
 Normal   SuccessfulRescale             7m29s               horizontal-Pod-autoscaler  New size: 5; reason: cpu resource utilization (percentage of request) above target
 Normal   SuccessfulRescale             2m25s               horizontal-Pod-autoscaler  New size: 8; reason: cpu resource utilization (percentage of request) above target

由于 CPU 资源利用率超过了 60%的目标,部署会自动扩展!添加更多 Pod 后,平均利用率将下降,因为并非所有 Pod 都在执行 CPU 压力测试。

对于 AKS 和 AKS Engine 集群,可以利用集群自动缩放器根据资源需求自动调整集群中节点的数量。您可以在官方 Azure 文档(docs.microsoft.com/en-us/azure/aks/cluster-autoscaler)和 Azure 上配置集群自动缩放器的指南中阅读更多信息(github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/azure/README.md)。

恭喜,您已成功为投票应用程序配置了 HPA。我们接下来要演示的 Kubernetes 功能是使用 ConfigMaps 和 Secrets 注入配置数据。

使用 ConfigMaps 和 Secrets 管理应用程序配置

为在 Kubernetes 上运行的应用程序提供配置,有几种可能的方法,记录在kubernetes.io/docs/tasks/inject-data-application/中:

  • 向容器命令传递参数

  • 为容器定义系统环境变量

  • 将 ConfigMaps 或 Secrets 挂载为容器卷

  • 可选地,使用 PodPresets 将所有内容包装起来。

本节将重点介绍使用 ConfigMaps 和 Secrets,它们在许多方面都很相似,但目的却非常不同。

首先,让我们来看看 Secrets。在几乎每个应用程序中,您都必须管理访问依赖项的敏感信息,例如密码、OAuth 令牌或证书。将这些信息作为硬编码值放入 Docker 镜像是不可能的,因为存在明显的安全问题和非常有限的灵活性。同样,直接在 Pod 清单文件中定义密码是不推荐的——清单文件应该保存在源代码控制中,绝对不是存储这种敏感信息的地方。为了管理这种类型的信息,Kubernetes 提供了 Secret 对象,它可以保存技术上任何类型的由键值对组成的数据。可选地,可以在etcd中对 Secrets 进行加密,这在生产场景中是推荐的。

现在,我们将演示如何使用kubectl创建一个通用(不透明)的 Secret。您也可以使用清单文件来实现这个目的,但是如何生成这些清单文件取决于您的 CI/CD 流水线(您不希望将这些清单文件提交到源代码控制中!)。要为 SQL Server 密码创建一个 Secret,请执行以下步骤:

  1. 打开一个 PowerShell 窗口。

  2. 假设您想在dev命名空间中创建一个名为mssql的 Secret,其中SA_PASSWORD键下保存着S3cur3P@ssw0rd,则执行以下命令:

kubectl create secret generic -n dev mssql --from-literal=SA_PASSWORD="S3cur3P@ssw0rd"
  1. 现在,该 Secret 可以作为容器中的卷(作为文件或目录)来使用,或者用于为容器定义环境变量。对于投票应用程序,更容易使用具有 SQL Server 密码的 Secret 作为环境变量。在部署清单中,可以通过以下方式实现这一点:
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  name: voting-application-frontend
  ...
spec:
  ...
  template:
    ...
    spec:
      ...
      containers:
      - name: frontend
        image: packtpubkubernetesonwindows/voting-application:1.2.0
        env:
        - name: MSSQL_SA_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mssql
              key: SA_PASSWORD
        - name: CONNECTIONSTRING_VotingApplication
          value: "Data Source=mssql-deployment;Initial Catalog=VotingApplication;MultipleActiveResultSets=true;User Id=sa;Password=$(MSSQL_SA_PASSWORD);"

这里的关键概念是使用secretKeyRef来引用我们刚刚创建的mssql Secret 中SA_PASSWORD键的值。该值被注入到MSSQL_SA_PASSWORD环境变量中(但是当使用kubectl describe时,您无法看到该值!),应用程序在容器中运行时可以访问该值。在我们的情况下,我们使用这个变量来定义另一个环境变量,名为CONNECTIONSTRING_VotingApplication。当您需要创建一个包含密码的连接字符串时,这是一个常见的模式,但请记住,这可能比使用卷更不安全。

在使用 Secrets 作为环境变量和作为挂载卷时,有一个重要的区别:通过卷提供的 Secret 数据将在 Secret 更改时进行更新。根据您的需求和实现细节,您可能希望选择将 Secrets 作为卷进行挂载。当然,这要求您的应用程序意识到 Secrets 文件可能发生变化,这意味着它需要积极监视文件系统,并刷新任何凭据提供者、连接字符串或证书,这些通常保存在内存中。将 Secrets 作为不可变的配置值是最佳选择(无论是作为卷挂载还是作为环境变量),这样可以使您的应用程序更可预测,更简单。但是,如果您的架构有限制,希望尽可能少地重新启动 Pod,那么将 Secrets 作为卷进行注入,并在应用程序中实现自动刷新可能是建议的解决方案。

从安全的角度来看,将 Secrets 作为环境变量注入在 Linux 上是不太安全的,因为当具有 root 权限时,您可以从/proc/<pid>/environ中枚举出一个进程的所有环境变量。在 Windows 节点上,问题更加复杂:您仍然可以访问进程的环境变量,但卷目前无法使用内存文件系统。这意味着 Secrets 会直接存储在节点的磁盘存储上。

为了存储应用程序的非敏感配置数据,Kubernetes 提供了 ConfigMap 对象。这是另一个概念,您可以使用它来完全解耦 Docker 镜像(构建产物)和运行时配置数据。从 API 的角度来看,这个概念类似于 Secrets——您可以存储键值对,并将它们注入到容器的环境变量中,或者使用卷将它们挂载为文件或目录。为了演示这一点,我们将创建一个 ConfigMap 来存储一个名为customErrors.config的配置文件,该文件在 ASP.NET MVC 应用程序的Web.config文件中被引用,并使用卷进行挂载。

如第四章中所述,Kubernetes 概念和 Windows 支持,截至 Kubernetes 1.17 版本,不支持在 Windows 上将卷subPath挂载为文件。这意味着无法轻松地使用 ConfigMap 覆盖整个 ASP.NET MVC 的Web.config文件。

请按照以下步骤操作:

  1. 首先,我们需要对投票应用程序源代码进行一些小的更改(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter11/10_voting-application-configmap-src)。我们将从<system.web>节点中提取<customErrors>节点到一个子目录中的单独文件中。在Web.config文件中,将<system.web>节点更改为:
  <system.web>
    <compilation debug="true" targetFramework="4.8" />
    <httpRuntime targetFramework="4.8" />
    <customErrors configSource="config\customErrors.config" />
  </system.web>
  1. config目录中创建customErrors.config文件,内容如下。我们将在接下来的步骤中使用 ConfigMap 进行覆盖:
<customErrors mode="On" />
  1. 使用1.3.0标签构建一个 Docker 镜像,并将其发布到 Docker Hub,就像之前的示例一样。

  2. 创建voting-application-customerrors-config.yaml清单文件,用于定义具有以下形式的 ConfigMap,并包含文件(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter11/10_voting-application-configmap-src/config/customErrors.config)作为data

kind: ConfigMap 
apiVersion: v1 
metadata: 
  namespace: dev 
  name: voting-application-customerrors-config
  labels: 
    app: voting-application
data: 
  customErrors.config: |
    <customErrors mode="On" />

可以使用kubectl命令以命令方式创建 ConfigMaps,但我们想演示 ConfigMap 清单文件的结构。重要的部分是在使用 YAML 多行字符串时保持正确的缩进以适应更大的配置文件(|)。

  1. 使用kubectl apply -f .\voting-application-customerrors-config.yaml命令应用清单文件。

  2. 修改voting-application.yaml清单文件以在容器中将我们的 ConfigMap 作为目录挂载(记得使用新的 Docker 镜像标签):

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  name: voting-application-frontend
  ...
spec:
  ...
  template:
    ...
    spec:
      ...
      containers:
      - name: frontend
        image: packtpubkubernetesonwindows/voting-application:1.3.0
        ...
        volumeMounts:
        - name: customerrors-config-volume
          mountPath: C:\inetpub\wwwroot\config\
        ...
      volumes:
      - name: customerrors-config-volume
        configMap:
          name: voting-application-customerrors-config

这里的重要部分是将voting-application-customerrors-config ConfigMap 作为卷(customerrors-config-volume)引用,并将其挂载到容器中的C:\inetpub\wwwroot\config\。如果当前在 Windows 上支持subPath挂载,我们可以只覆盖单个文件而不是整个目录。

  1. 使用kubectl apply -f .\voting-application.yaml命令应用清单文件。

  2. 现在,在浏览器中导航到http://<serviceExternalIp>/Home/StressCpu地址。这将触发一个异常-我们没有在 URL 中提供所需的请求参数。您应该会看到一个自定义错误页面,只是通知“在处理您的请求时发生错误”。

  3. 关闭自定义错误页面,并修改voting-application-customerrors-config.yaml清单文件的 ConfigMap,使其包含节点:

  customErrors.config: |
    <customErrors mode="Off" />
  1. 使用kubectl apply -f .\voting-application-customerrors-config.yaml命令应用清单文件。

根据 IIS 是否能够监视C:\inetpub\wwwroot\config\目录中的更改,IIS 应用程序池可能不会在 Pod 中重新加载。在这种情况下,exec进入容器并执行Restart-WebAppPool DefaultAppPool命令。

  1. 再次导航到http://<serviceExternalIp>/Home/StressCpu。如果您的 IIS 应用程序池已重新加载,您将看到完整的异常详细信息,而不是自定义错误页面。

通过这种方式,我们已经演示了如何在 Windows Pods 中使用 Secrets 和 ConfigMaps。现在,是时候熟悉在 Windows 节点上管理持久数据存储了。

在 Windows 节点上管理持久数据存储

在第四章 Kubernetes 概念和 Windows 支持中,我们已经涵盖了 Kubernetes 中一些与存储相关的概念,如PersistentVolumesPV)、PersistentVolumeClaimsPVC)和StorageClassesSC),以及它们在 Windows 工作负载中的支持。在容器化应用程序中管理状态和存储以及使用 StatefulSets 是一个广泛且复杂的主题,不在本书的范围内——官方文档提供了一个很好的介绍,可以在kubernetes.io/docs/concepts/storage/找到。对于 Windows Pods 的 PersistentVolume 支持的关键要点是,您可以使用一些现有的卷插件,但不是全部。在 Windows 上,支持以下内容:

  • 树内卷插件:azureDisk、azureFile、gcePersistentDisk、awsElasticBlockStore(自 1.16 版起)和 vsphereVolume(自 1.16 版起)

  • FlexVolume 插件:SMB 和 iSCSI

  • CSI 卷插件(树外插件)

这意味着,对于 Windows 节点,在 AKS 或 AKS Engine 集群的情况下,您只能使用 azureDisk 和 azureFile in-tree 卷插件,从技术上讲,您可以将 FlexVolume SMB 插件与 Azure Files SMB 共享相结合。对于本地场景,您必须依赖于配置为使用自己的存储或连接到作为外部云服务公开的 SMB 共享的 FlexVolume SMB 或 iSCSI 插件。如果您在 vSphere 上运行,当然可以利用 vsphereVolume 插件。总的来说,在本地运行的混合 Windows/Linux 集群中处理持久卷仍然很困难。

对于本地集群,使用 Rook(rook.io/)来编排存储并与 Kubernetes 集成是一个很好的解决方案。不幸的是,即使是用于消耗卷的 Windows 也没有支持。

我们的投票应用程序已经在 Linux Pod 中运行 SQL Server 时使用了 PersistentVolumes - 在这种情况下,我们一直在使用 StorageClass 与kubernetes.io/azure-disk供应程序,它在内部使用 azureDisk 卷插件。这种情况涉及 Linux Pod - 现在,我们将为 Windows Pod 使用 PersistentVolumes。投票应用程序在前端容器中没有特定的数据持久化需求,但作为一个纯粹的例子,我们将展示如何为每个 Pod 存储一个投票日志。

此更改的源代码可在github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter11/12_voting-application-persistentvolume-src上找到。我们不会详细介绍实现细节,但更改很简单:

要部署应用程序,请执行以下步骤:

  1. 为投票应用程序构建一个标记为1.4.0的 Docker 镜像,并像之前的示例一样将其推送到 Docker Hub。

  2. 我们需要将部署转换为 StatefulSet。因此,您首先需要从集群中删除部署:

kubectl delete deployment -n dev voting-application-frontend
  1. 创建StorageClass清单sc.yaml,内容如下。我们将使用kubernetes.io/azure-disk提供程序来使用 azureDisk 卷插件:
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: azure-disk
provisioner: kubernetes.io/azure-disk
parameters:
  storageaccounttype: Standard_LRS
  kind: Managed
  1. 使用kubectl apply -f sc.yaml命令应用清单文件。

  2. 将部署转换为 StatefulSet,并使用 Docker 镜像的1.4.0版本。完整的清单文件可以在github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter11/13_persistentvolume/voting-application.yaml找到。我们将需要的更改与之前的voting-application.yaml清单文件进行对比,如下所示:

apiVersion: apps/v1
kind: StatefulSet
...
spec:
  replicas: 5
  serviceName: voting-application-frontend  # (1)
  ...
  template:
    ...
    spec:
      ...
      initContainers:  # (2)
      - name: volume-mount-permissions-fix
        image: packtpubkubernetesonwindows/voting-application:1.4.0
        command: ["powershell.exe", "-Command", "iisreset.exe /START; icacls.exe c:\\data /grant '\"IIS AppPool\\DefaultAppPool\":RW'"]
        volumeMounts:
        - mountPath: C:/data
          name: voting-log-volume
      containers:
      - name: frontend
        image: packtpubkubernetesonwindows/voting-application:1.4.0
        ...
        volumeMounts:  # (3)
        - mountPath: C:/data
          name: voting-log-volume
  volumeClaimTemplates:  # (4)
  - metadata:
      name: voting-log-volume
      labels:
        app: voting-application
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 100Mi
      storageClassName: azure-disk

StatefulSet 需要提供负责此 StatefulSet 的服务名称(1)。除此之外,我们还定义了volumeClaimTemplates4),用于为此 StatefulSet 中的每个 Pod 副本创建专用的 PersistentVolumeClaim。我们引用此 PVC 来将卷挂载为容器中的C:/data目录(3),其中voting.log将被持久化。此外,我们还需要为 IIS App Pool 用户提供适当的读/写权限以访问C:/data目录,否则 Web 应用程序将无法访问我们的 PersistentVolume。这是通过在init容器(2)中执行icasls.exe来实现的。请注意,您需要首先启动 IIS(iisreset.exe /START)以便在分配权限之前正确创建 IIS App Pool 用户!

  1. 使用kubectl apply -f .\voting-application.yaml命令应用清单文件。

  2. 当 StatefulSet 准备就绪时,打开网页浏览器并投几次票。

  3. 在网页浏览器中打开http://<serviceExternalIp>/Home/VotingLog,根据您到达的 Pod 副本不同,您将看到不同的结果:

好的,现在我们知道在容器中写入目录的操作正常工作。但让我们证明这个目录确实由 PersistentVolume 挂载支持。为此,请执行以下步骤:

  1. statefulset缩减到0个副本。这将删除 StatefulSet 的所有 Pod:
kubectl scale statefulset/voting-application-frontend -n dev --replicas=0
  1. 等待所有 Pod 终止,并使用kubectl get pods -n dev命令观察。

  2. 扩展statefulset,例如,到5个副本:

kubectl scale statefulset/voting-application-frontend -n dev --replicas=5
  1. 等待 Pod 创建并变为就绪。由于我们的就绪探针,这可能需要几分钟。

  2. 在网页浏览器中导航至http://<serviceExternalIp>/Home/VotingLog。您应该看到每个 Pod 副本的投票日志完全相同。这表明所有 Pod 都像以前一样挂载了相同的 PersistentVolumes。

恭喜!您已成功在 Windows Pod 中为投票应用程序挂载了 azureDisk 持久卷。接下来,我们将看看如何为您的应用程序配置滚动更新。

Deployments配置滚动更新

在生产场景中,您肯定需要一种部署策略,为您的应用程序提供零停机更新。作为容器编排器,Kubernetes 提供了不同的构建模块,可用于实现蓝绿部署、金丝雀部署或滚动部署。Kubernetes 部署对象完全支持执行滚动更新部署——在这种部署类型中,应用程序的新版本通过逐渐交换旧副本与新副本来推出,所有这些副本都在同一个服务后面。这意味着,在推出过程中,最终用户将访问应用程序的旧版本或新版本之一。

为了确保在 Kubernetes 中对部署进行真正的零停机更新,您需要配置适当的探测器,特别是就绪性。通过这种方式,只有当副本能够正确响应请求时,用户才会被重定向到一个副本。

让我们看看如何为投票应用程序实现滚动部署。实际上,在先前的示例中,我们已经在使用这种方法,现在我们将更详细地解释配置。按照以下步骤:

  1. 使用 kubectl delete statefulset -n dev voting-application-frontend 命令删除我们在上一节中创建的 StatefulSet。

  2. 让我们回到我们用于 HPA 演示的 voting-application.yaml 部署清单文件。您可以在 GitHub 仓库中找到该文件,网址为 github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter11/14_rollingupdate/voting-application.yaml

  3. 滚动更新部署的配置如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  name: voting-application-frontend
  ...
spec:
  replicas: 5
  minReadySeconds: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  ...
    spec:
      ...
      containers:
      - name: frontend
        image: packtpubkubernetesonwindows/voting-application:1.2.0
        ...

为了为 Deployment 对象定义滚动更新部署的关键部分是strategy。要配置滚动更新,您需要使用typeRollingUpdate值(这也是默认值)。另一种方法是使用 recreate,它将简单地杀死所有 Pod,然后创建新的 Pod——通常情况下,除非与更复杂的模式(如蓝绿部署)结合使用,您不希望在生产中使用这种策略类型。对于RollingUpdate类型,您可以定义maxUnavailable,它表示在更新期间有多少个 Pod 可以处于非就绪状态。同样,maxSurge定义了在部署期间可以创建的 Pod 的最大数量,超过所需 Pod 数量。您可以将这些值指定为数字或百分比——默认情况下,它们都设置为 25%。为了更好地理解这些数字在实践中的含义,让我们分析一下我们的例子。当您触发 Deployment 的部署时,希望的副本数量为5,可能会发生以下事件序列:

    • 创建了一个新的 Pod。现在,我们总共有六个 Pod,所以我们已经达到了maxSurge设置的限制。
  • maxUnavailable设置为1,我们有五个就绪的 Pod,所以可以终止一个旧的 Pod。我们总共有五个 Pod,其中四个是就绪的。

  • 创建了一个新的 Pod。现在我们总共有六个 Pod,但只有四个是就绪的。部署必须等待更多的 Pod 就绪才能继续。

  • 其中一个新的 Pod 就绪了。我们总共有六个 Pod,其中五个是就绪的,这意味着一个旧的 Pod 可以被终止,然后创建一个新的 Pod。

  • 这个过程逐渐持续,直到所有五个新的 Pod 都就绪为止。

  1. 让我们看看它在实践中是如何工作的。首先,使用kubectl apply -f .\voting-application.yaml命令应用清单文件——这将创建应用的初始版本。

  2. 对现有部署的滚动更新可以通过实时编辑对象或使用kubectl rollout命令来进行。一般来说,最好使用声明性方法:更改清单文件,然后再次应用。在清单文件中将容器镜像标签更改为packtpubkubernetesonwindows/voting-application:1.4.0,然后使用kubectl apply -f .\voting-application.yaml命令进行应用。

  3. 在那之后,立即开始使用以下命令观察rollout status

PS C:\src> kubectl rollout status -n dev deployment/voting-application-frontend
Waiting for deployment "voting-application-frontend" rollout to finish: 2 out of 5 new replicas have been updated...
Waiting for deployment "voting-application-frontend" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "voting-application-frontend" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "voting-application-frontend" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "voting-application-frontend" rollout to finish: 4 of 5 updated replicas are available...
deployment "voting-application-frontend" successfully rolled out
  1. 在部署过程中,您可以使用诸如kubectl rollout undo -n dev deployment/voting-application-frontendkubectl rollout pause -n dev deployment/voting-application-frontend之类的命令来控制部署的滚动。但是,您也可以通过修改清单文件并再次应用来实现相同的效果,甚至包括暂停。

  2. 您可以在部署过程中尝试访问应用程序。我们已经正确配置了就绪探针,因此您不会遇到应用程序的意外响应!

StatefulSets 也具有可定制的部署策略。由于状态持久性,该策略与部署的策略有些不同。您可以在官方文档中阅读更多内容,网址为kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies

现在,让我们专注于 Kubernetes 中的另一个重要主题:基于角色的访问控制(RBAC)。

基于角色的访问控制

Kubernetes 带有内置的 RBAC 机制,允许您配置细粒度的权限集并将其分配给用户、组和服务账户(主体)。通过这种方式,作为集群管理员,您可以控制集群用户(内部和外部)与 API 服务器的交互方式,他们可以访问哪些 API 资源以及可以执行哪些操作(动词)。

Kubernetes 中的身份验证是高度可配置和可扩展的;您可以在官方文档中阅读更多内容,网址为kubernetes.io/docs/reference/access-authn-authz/authentication/。在 AKS Engine 集群中,可以轻松集成 Azure Active Directory(AAD);您可以在github.com/Azure/aks-engine/blob/master/docs/topics/aad.md找到更多详细信息。

使用 RBAC 涉及两组 API 资源:

  • RoleClusterRole:它们定义了一组权限。Role中的每个规则都说明了允许对哪些 API 资源使用哪些动词。RoleClusterRole之间唯一的区别是Role是命名空间范围的,而ClusterRole不是。

  • RoleBindingClusterRoleBinding:它们将用户或一组用户与给定角色关联起来。类似地,RoleBinding是命名空间范围的,ClusterRoleBinding是集群范围的。ClusterRoleBindingClusterRole配合使用,RoleBindingClusterRoleRole配合使用。

Kubernetes 使用宽松的 RBAC 模型 - 没有拒绝规则;默认情况下拒绝一切,并且您必须定义允许规则。RBAC 的使用有详细的文档,并且所有功能都在官方文档中介绍,可在kubernetes.io/docs/reference/access-authn-authz/rbac/上找到。您应该考虑 RBAC 策略的两个关键点:

  • 使用最小权限原则。您的应用程序应仅访问其自己的资源(建议您使用具有对该应用程序的 Secrets 或 ConfigMaps 访问权限的专用服务帐户来运行每个应用程序)。用户应根据其在项目中的角色拥有受限制的访问权限(例如,QA 工程师可能只需要对集群具有只读访问权限)。

  • RoleBinding分配给组而不是单个用户。这将使您的权限管理更加容易。请注意,这需要与外部身份验证提供程序集成才能发挥最佳作用。

让我们演示如何使用RoleRoleBinding来限制对部署的访问权限,使其仅能访问最少的所需 ConfigMaps 和 Secrets。我们将为 ASP.NET MVC 应用程序执行此操作,并且使用类似的方法可以作为额外的练习用于 SQL Server。为此,我们将使用用于演示 ConfigMaps 的投票应用程序 Docker 镜像packtpubkubernetesonwindows/voting-application:1.3.0。此部署在运行时需要 ConfigMaps 和 Secrets。请按照以下步骤配置 RBAC:

  1. 创建serviceaccount.yaml清单文件,用于专用 ServiceAccount,命名为voting-application
apiVersion: v1
kind: ServiceAccount
metadata:
  name: voting-application
  namespace: dev
  1. 使用kubectl apply -f .\serviceaccount.yaml命令应用清单文件。

  2. Role创建role.yaml清单文件,用于读取应用程序的 Secrets 和 ConfigMaps:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: voting-application-data-reader
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["voting-application-customerrors-config"]
  verbs: ["get"]
- apiGroups: [""]
  resources: ["secret"]
  resourceNames: ["mssql"]
  verbs: ["get"]
  1. 使用kubectl auth reconcile -f .\role.yaml命令来应用Role。建议使用kubectl auth reconcile而不是kubectl apply

  2. RoleBinding创建rolebinding.yaml清单文件,将我们的 ServiceAccount 与前面的角色关联起来:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev
  name: voting-application-data-reader
subjects:
- kind: ServiceAccount
  name: voting-application
roleRef:
  kind: Role
  name: voting-application-data-reader
  apiGroup: rbac.authorization.k8s.io
  1. 使用kubectl auth reconcile -f .\rolebinding.yaml命令应用RoleBinding

  2. 检查 RBAC 是否允许 ServiceAccount 访问 ConfigMap。您可以使用kubectl auth can-i get configmap/voting-application-customerrors-config -n dev --as system:serviceaccount:dev:voting-application命令,或者使用kubectl auth can-i --list -n dev --as system:serviceaccount:dev:voting-application命令可视化所有可访问的 API 资源。

  3. 修改voting-application.yaml清单文件,使部署使用voting-application ServiceAccount:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  name: voting-application-frontend
  ...
spec:
  ...
  template:
    ...
    spec:
      serviceAccountName: voting-application
      ...
  1. 使用kubectl apply -f .\voting-application.yaml命令应用部署清单文件。

您可以执行类似的操作,例如通过定义允许对所有 API 资源进行只读访问的角色来为集群中的用户进行操作。

恭喜!您已成功为投票应用程序设置了 RBAC。

摘要

在本章中,我们演示了 Kubernetes 的几个常用高级功能。首先,您了解了 Kubernetes 中命名空间的目的以及如何管理它们。然后,我们介绍了就绪、存活和启动探针,这些用于监视 Pod 容器的生命周期,并为您提供了一组在处理探针时的推荐实践以及如何避免常见陷阱。接下来是学习如何指定 Pod 资源请求和限制,以及如何结合 HPA 进行自动缩放。为了将配置数据(包括敏感密码)注入到我们的应用程序中,我们使用了 ConfigMaps 和 Secrets。除此之外,我们还演示了如何在运行在 Windows 节点上的 StatefulSets 中使用 PersistentVolumes(由 azureDisk Volume 插件支持)。最后,您了解了如何处理部署对象的滚动更新,以及 Kubernetes 中 RBAC 的目的。

下一章将重点介绍使用 Kubernetes 的开发工作流程,以及在创建 Kubernetes 应用程序时如何与其他开发人员合作。

问题

  1. 何时应考虑使用 Kubernetes 命名空间?

  2. 就绪和存活探针之间有什么区别?

  3. 使用不当配置的存活探针有哪些风险?

  4. Pod 容器的资源requestslimits值有什么区别?

  5. HPA 中冷却延迟的目的是什么?

  6. ConfigMaps 和 Secrets 之间有什么区别?

  7. StatefulSet 规范中的volumeClaimTemplates是什么?

  8. 在使用滚动更新部署时,为什么要确保就绪探针的正确配置?

  9. 在 Kubernetes 中使用 RBAC 时,最重要的经验法则是什么?

您可以在本书的评估部分找到这些问题的答案。

进一步阅读

第十二章:使用 Kubernetes 的开发工作流程

让我们面对现实吧——Kubernetes 应用程序开发并不简单。在前几章中,我们主要关注了 Kubernetes 的集群供应和运营方面,这本身就有其复杂性。作为在 Windows 上使用 Kubernetes 的软件开发人员,您将面临完全不同的挑战。事实上,您可能需要调整设计方法,采用云优先、云原生、Kubernetes 优先或其他现代方法。您已经看到,Kubernetes 擅长处理从未设计时就意味着要在容器中托管的 Windows 应用程序,但要充分利用 Kubernetes 的功能,您必须扭转这种依赖关系,开始将 Kubernetes 视为设计的中心和开发环境。

在本章中,我们将演示一些流行的工具,您可以在 Windows 上的开发工作流程中使用,从 Visual Studio 2019 和 Visual Studio Code 的基本集成开始,到使用 Azure Application Insights 进行高级快照调试结束。您还将学习如何使用 Helm(版本 3)为您的 Kubernetes 应用程序创建可再分发的软件包。最后,我们将介绍 Azure Dev Spaces,它极大地简化了整个团队的 Kubernetes 开发。

本章将重点讨论以下主题:

  • 使用 Kubernetes 的开发工具

  • 使用 Helm 打包应用程序

  • 使用 Azure Application Insights 调试容器化应用程序

  • 使用 Kubernetes 仪表板

  • 使用 Azure Dev Spaces 团队中的微服务开发

技术要求

对于本章,您将需要以下内容:

  • 已安装 Windows 10 Pro、Enterprise 或 Education(1903 版或更高版本;64 位)。

  • Microsoft Visual Studio 2019 Community(或任何其他版本),如果您想编辑应用程序的源代码并对其进行调试。请注意,对于快照调试器功能,您需要企业版。

  • Microsoft Visual Studio Code,如果您想使用图形界面管理 Kubernetes 集群。

  • Windows 的 Chocolatey 软件包管理器(chocolatey.org/)。

  • Azure 账户。

  • 使用 Azure Kubernetes Service(AKS)引擎部署的 Windows/Linux Kubernetes 集群,准备部署前几章中的投票应用程序。

使用 Chocolatey 软件包管理器并非强制性,但它可以使安装过程和应用程序版本管理变得更加容易。安装过程在chocolatey.org/install中有文档记录。

要跟随操作,您需要自己的 Azure 帐户以创建 Kubernetes 集群的 Azure 资源。如果您尚未为之前的章节创建帐户,您可以阅读有关如何获取个人使用的有限免费帐户的更多信息,网址为azure.microsoft.com/en-us/free/

使用 AKS 引擎部署 Kubernetes 集群已在第八章中进行了介绍,部署混合 Azure Kubernetes 服务引擎集群。将 Voting 应用程序部署到 Kubernetes 已在第十章中进行了介绍,部署 Microsoft SQL Server 2019 和 ASP.NET MVC 应用程序

您可以从官方GitHub存储库中下载本书章节的最新代码示例,网址为github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter12

使用 Kubernetes 的开发工具

在 Windows 上日常开发.NET 应用程序时,您很可能会使用 Visual Studio 2019 或 Visual Studio Code。在本节中,我们将向您展示如何安装用于 Kubernetes 的附加扩展,以便为容器编排器引导应用程序。

Visual Studio 2019 和 Visual Studio Code 目前对在 Kubernetes 中管理 Windows 容器的支持非常有限。您将无法使用大多数功能,例如与 Azure Dev Spaces 集成,尽管这可能会在未来发生变化。在.NET Core 的情况下,您可以在 Windows 上开发并依赖于 Linux Docker 镜像。

首先,让我们看看如何在 Visual Studio 2019 中启用 Kubernetes 支持。

Visual Studio 2019

最新版本的 Visual Studio 带有预定义的 Azure 开发工作负载,您可以直接从 Visual Studio 安装程序应用程序轻松安装。您无需安装任何其他扩展即可在 Visual Studio 中获得 Kubernetes 支持。

如果您之前在 Visual Studio 的早期版本中使用过 Visual Studio Tools for Kubernetes(现已弃用),那么您可以在最新版本的 Visual Studio 的 Azure 开发工作负载中期望类似的功能。

要安装 Azure 开发工作负载,请按照以下步骤操作:

  1. 在 Windows 的开始菜单中,搜索 Visual Studio Installer 应用程序。

  2. 选择您的 Visual Studio 版本,点击更多,然后选择修改。

  3. 选择 Azure 开发并通过点击修改来接受更改:

  1. 等待安装完成并启动 Visual Studio。

现在,您可以为 Kubernetes 的容器应用程序创建新项目,其中包括以下内容:

  • ASP.NET Core

  • 用于打包的 Helm 图表

  • 用于在 AKS 上进行快速迭代开发的 Azure Dev Spaces

也可以为现有的 ASP.NET Core 添加 Kubernetes/Helm 支持:

  1. 在解决方案资源管理器中右键单击项目。

  2. 导航到添加|容器编排器支持

  3. 选择 Kubernetes/Helm。

不幸的是,Visual Studio 2019 目前对于管理 Kubernetes 集群的功能有限。作为替代,您可以使用 Visual Studio Code 来完成这项任务。

Visual Studio Code

对于 Visual Studio Code,您可以使用微软提供的两个官方扩展:

  • Kubernetes (ms-kubernetes-tools.vscode-kubernetes-tools):使您能够在树视图中探索 Kubernetes 集群,管理 Kubernetes 对象,并为编辑清单文件和 Helm 图表提供智能感知。

  • Azure Dev Spaces (azuredevspaces.azds):启用 Azure Dev Spaces 集成,类似于您在 Visual Studio 2019 中的功能。

要安装这两个扩展,打开 Visual Studio Code 并按照以下步骤操作:

  1. 打开扩展面板(Ctrl+Shift+X)。

  2. 在 Marketplace 中搜索 Kubernetes。

  3. 点击安装。

  4. 重复相同步骤以安装 Azure Dev Spaces。

在右侧菜单中,您现在可以使用 Kubernetes 面板,它会自动加载您的 kubeconfig。这个扩展特别适用于处理包含清单文件的工作空间,因为您会得到自动完成、YAML 语法高亮显示和验证。

您可以从树视图或使用命令(Ctrl+Shift+P)来管理您的集群——这可以代替在 PowerShell 中执行kubectl命令。例如,您可以查看容器的日志:

Visual Studio Code 目前是 Windows 中用于处理 Kubernetes 和 Helm 的最有用和最先进的工具。 在下一节中,我们将展示如何使用 Helm 打包应用程序。

使用 Helm 打包应用程序

应用程序需要打包以便轻松重新分发和管理依赖关系。 在 Windows 中,您可以使用 Chocolatey,在 Ubuntu 中,您可以使用APT(Advanced Package Tool 的缩写),而对于 Kubernetes,您可以使用 Helm 作为包管理器(v3.helm.sh/)。 还有一些替代方案,比如 Kustomize(它在kubectl中具有本机支持)和 Kapitan,但总的来说,Helm 目前被认为是行业标准,拥有最大的 Helm 图表官方存储库(github.com/helm/charts)。

Helm 的主要用例如下:

  • 将流行软件部署到您的 Kubernetes 集群。 软件包以 Helm 图表的形式分发。

  • 共享您自己的应用程序作为 Helm 图表。 这可以包括为最终用户打包产品或将 Helm 用作系统中微服务的内部包和依赖项管理器。

  • 确保应用程序获得适当的升级,包括依赖关系管理。

  • 为您的需求配置软件部署。 Helm 图表基本上是 Kubernetes 清单的通用另一种标记语言YAML)参数化模板。 Helm 使用 Go 模板(godoc.org/text/template)进行参数化。 如果您熟悉 Go,那么您会感到很亲切; 如果不熟悉,那么您会发现它与其他模板系统非常相似,比如 Mustache。

请注意,Helm 的架构在最近发布的 3.0.0 版本中发生了 drastical 变化。

以前,Helm 需要在 Kubernetes 上部署一个名为 Tiller 的专用服务,负责与 Kubernetes API 的实际通信。 这引发了各种问题,包括安全性和 RBAC(Role-Based Access Control 的缩写)问题。 从 Helm 3.0.0 开始,不再需要 Tiller,图表管理由客户端完成。 您可以在官方 FAQ 中阅读有关旧版 Helm 版本之间的区别的更多信息helm.sh/docs/faq/#changes-since-helm-2

Helm 被分发为一个带有类似 kubectl 的 CLI 的客户端(库)。现在可以使用客户端执行 Helm 中的所有操作。让我们在您的 Windows 机器上安装 Helm。

安装 Helm

建议在 Windows 机器上使用 Chocolatey 安装 Helm。要安装 Helm,请按照以下步骤进行:

  1. 以管理员身份打开 PowerShell 窗口。

  2. 执行以下安装命令:

 choco install kubernetes-helm
  1. 安装完成后,请验证您是否运行版本3.0.0或更高版本:
PS C:\src> helm version
version.BuildInfo{Version:"v3.0.0", GitCommit:"e29ce2a54e96cd02ccfce88bee4f58bb6e2a28b6", GitTreeState:"clean", GoVersion:"go1.13.4"}
  1. 检查是否有使用helm repo list命令添加的任何存储库。如果没有(在版本 3.0.0 中),添加官方的stable存储库并更新:
helm repo add stable https://kubernetes-charts.storage.googleapis.com
helm repo update
  1. 现在,尝试搜索一些 Helm 图表,例如,让我们检查是否有 Microsoft SQL Server 的图表:
PS C:\src> helm search hub mssql
URL                                             CHART VERSION   APP VERSION     DESCRIPTION
https://hub.helm.sh/charts/stable/mssql-linux   0.10.1          14.0.3023.8     SQL Server 2017 Linux Helm Chart

Helm Hub (hub.helm.sh/)提供了一个用户友好的界面,用于浏览官方 Helm 存储库(github.com/helm/charts)。

我们找到了一个在 Linux 容器中运行的 SQL Server 的稳定图表。它基于 2017 年的版本,但我们仍然可以将其用于我们的投票应用程序。

使用 Helm 部署 Microsoft SQL Server

现在让我们来看看如何将 Microsoft SQL Server 部署到我们的 AKS Engine 集群。每个图表的结构都是相似的:

  • root目录中,您可以找到一个详细的自述文件,其中描述了如何安装图表以及可能的参数是什么(github.com/helm/charts/tree/master/stable/mssql-linux)。

  • Chart.yaml文件包含图表元数据,包括依赖信息。

  • templates目录包含所有用于 Kubernetes 清单的 Go 模板。

  • values.yaml文件定义了可以使用 CLI 参数或提供 YAML 文件来覆盖的图表的默认值。

安装 Helm 图表的过程很简单:为您的需求定义正确的值(可能需要分析模板以了解发生了什么),然后运行helm install命令。查看 SQL Server 的图表,我们需要指定以下values.yaml文件:

acceptEula:
  value: "y"

edition:
  value: Developer

sapassword: "S3cur3P@ssw0rd"

service:
  type: LoadBalancer

persistence:
  enabled: true
  storageClass: azure-disk

nodeSelector:
  "kubernetes.io/os": linux

使用 Helm 部署 SQL Server,请按照以下步骤进行:

  1. 打开 PowerShell 窗口。

  2. 将前面的文件保存为values.yaml在当前目录中。

  3. 创建先决条件。我们需要dev-helm命名空间和azure-disk StorageClass。创建以下prereq.yaml清单文件:

---
kind: Namespace
apiVersion: v1
metadata:
  name: dev-helm
  labels:
    name: dev-helm
---
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: azure-disk
provisioner: kubernetes.io/azure-disk
parameters:
  storageaccounttype: Standard_LRS
  kind: Managed
  1. 使用kubectl apply -f .\prereq.yaml命令应用清单文件。

  2. 执行 Helm 图表安装的干运行。您将能够看到将应用哪些 Kubernetes 清单文件:

helm install demo-mssql stable/mssql-linux `
 --namespace dev-helm `
 --values .\values.yaml `
 --debug `
 --dry-run

此命令将执行stable/mssql-linux作为demo-mssql Helm 发布在dev-helm命名空间的安装干运行。

  1. 如果您对结果满意,请执行安装:
helm install demo-mssql stable/mssql-linux `
 --namespace dev-helm `
 --values .\values.yaml
  1. 使用以下命令观察 SQL Server 的部署:
kubectl get all --all-namespaces -l release=demo-mssql
  1. 您还可以使用 Helm CLI 检查状态:
helm status -n dev-helm demo-mssql
  1. 使用 SQL Server Management Studio 或 SQL Tools 容器验证 SQL Server 是否正常运行。您可以使用服务的外部 IP 地址——我们已经暴露了一个负载均衡器服务。

管理 Helm 发布的命令与 kubectl 类似,都是命名空间范围的。

正如你所看到的,使用 Helm 在集群中快速引导复杂的应用程序非常高效。现在,让我们为我们的投票应用程序准备一个 Helm 图表。我们将使用一个 SQL Server 图表作为依赖。

为我们的投票应用程序创建一个 Helm 图表

为了将我们的投票应用程序打包为 Helm 图表,我们将使用上一章中用于水平 Pod 自动缩放演示的清单文件。您可以在书的 GitHub 存储库中找到基本的清单文件。

为了准备 Helm 图表,我们需要按照以下步骤进行:

  1. 收集所有必需的 Kubernetes 清单文件,并确定哪些部分应该被参数化。我们将用这些来创建 Helm 模板文件和“默认值”文件。

  2. 为我们的应用程序定义所有的依赖关系,并为它们定义适当的参数值。我们将把这些参数注入到我们父图表的“默认值”文件中。

  3. 将 Entity Framework 数据库迁移转换为安装后和升级后的 Helm 钩子。

这里的大部分工作是将原始的 Kubernetes 清单文件转换为 Helm 模板。在接下来的几个步骤中,我们将只展示这个过程的相关部分。为了获得最佳的编码体验,请使用 Visual Studio Code 编辑 Helm 图表。您可以在 Github 存储库github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter12/03_voting-application-helm中找到我们投票应用程序的最终 Helm 图表。

请按照以下步骤创建您的 Helm 图表:

  1. 首先,让我们从集群中卸载 SQL Server Helm 发布。我们将自动将此图表作为 Voting 应用程序父图表的依赖项安装:
helm uninstall -n dev-helm demo-mssql
  1. 运行以下命令创建 Helm 图表脚手架:
helm create voting-application

这将创建一个名为voting-application的目录,其中包含 Helm 图表的基本结构和模板。我们将重复使用其中的大部分内容。

  1. 使用cd .\voting-application\导航到图表目录,并修改Chart.yaml文件中的图表元数据:
apiVersion: v2
name: voting-application
description: Voting Application (Windows Containers) Helm chart
type: application
version: 0.1.0
appVersion: 1.4.0
dependencies:
  - name: mssql-linux
    version: 0.10.1
    repository: https://kubernetes-charts.storage.googleapis.com
sources:
- https://github.com/hands-on-kubernetes-on-windows/voting-application

这里代码的最重要部分涉及定义适当的依赖关系和设置适当的apiVersion,这将在模板中用作 Docker 镜像标签。从官方稳定存储库https://kubernetes-charts.storage.googleapis.com中添加mssql-linux的最新图表版本(0.10.1)。

  1. 使用cd .\templates\命令导航到templates目录。我们将在原始形式中使用reuse _helpers.tpl(其中包含模板助手函数)、service.yamlserviceaccount.yamlingress.yaml。这些清单模板将产生我们需要的内容,无需任何更改。

  2. 下一步是为我们的 Deployment 定义一个清单模板,命名为deployment.yaml;您应该检查图表脚手架中的原始deployment.yaml文件,因为您可以在我们的模板中使用其中的大部分内容。此模板的最终版本可以在github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter12/03_voting-application-helm/templates/deployment.yaml找到。例如,让我们解释一下如何对 Docker 镜像标签进行参数化并注入 SQL Server 密码:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "voting-application.fullname" . }}
  labels:
    {{- include "voting-application.labels" . | nindent 4 }}
spec:
  ...
  template:
    ...
    spec:
      ...
      containers:
        - name: {{ .Chart.Name }}-frontend
          ...
          image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }} 
          env:
          - name: MSSQL_SA_PASSWORD
            valueFrom:
              secretKeyRef:
                name: {{ .Release.Name }}-mssql-linux-secret
                key: sapassword
          - name: CONNECTIONSTRING_VotingApplication
            value: "Data Source={{ .Release.Name }}-mssql-linux;Initial Catalog=VotingApplication;MultipleActiveResultSets=true;User Id=sa;Password=$(MSSQL_SA_PASSWORD);"

让我们一步一步地分析。{{ include "voting-application.fullname" . }}短语向您展示了如何包含在_helpers.tpl中定义的模板,并将其用作部署名称。如果有更高级的模板逻辑,您应该始终使用此文件来定义可重用的模板。

Pod 容器的 Docker 镜像定义为"{{ .Values.image.repository }}:{{ .Chart.AppVersion }}";您可以使用.Values来引用在values.yaml文件中定义的变量,使用.Chart来引用图表元数据。最后,我们使用了{{ .Release.Name }}-mssql-linux-secret来引用由依赖的 SQL Server 图表创建的秘密。

您需要了解依赖图表的内部结构,以了解应使用什么值(github.com/helm/charts/blob/master/stable/mssql-linux/templates/secret.yaml)。

不幸的是,Helm 没有一个简单的引用过程来从依赖图表中获取这些值,因此您必须要么按照 Helm 使用的约定硬编码名称(我们这样做了),要么在_helpers.tpl中定义一个专用模板(这是一种更清晰的方法,但也更复杂)。

  1. 定义 RBAC 角色和 RoleBindings,我们创建了两个额外的模板文件,rolebinding.yamlrole.yaml。您可以在github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter12/03_voting-application-helm/templates中找到内容。为应用程序定义 RBAC 清单可以是有条件的;您可以在官方 Helm 图表中看到这种做法。

  2. 我们需要定义的最后一个清单是用于运行 Entity Framework 数据库迁移的 Helm 钩子(helm.sh/docs/topics/charts_hooks/)(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter12/03_voting-application-helm/templates/post-install-job.yaml)。钩子就像任何其他清单模板一样,但它具有额外的注释,确保清单在图表发布的生命周期的某个特定点应用。此外,如果钩子是 Kubernetes 作业,Helm 可以等待作业完成并进行清理。我们希望这个钩子是一个作业,与我们已经用于 EF 迁移的相同类型,并且希望它在安装或升级发布后执行。让我们看看如何在post-install-job.yaml文件中定义我们作业的注释:

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-ef6-database-migrate
  ...
  annotations:
    "helm.sh/hook": post-install,post-upgrade
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  backoffLimit: 10

将清单模板转换为钩子的关键注释是"helm.sh/hook"。我们使用post-installpost-upgrade值来确保钩子在安装后和 Helm 发布升级后执行。"helm.sh/hook-weight"短语用于确定钩子的顺序,在我们的情况下并不重要,因为我们只有一个钩子。

"helm.sh/hook-delete-policy"短语定义了作业实例应在何种情况下自动删除。我们希望仅在成功的钩子执行时删除它们;否则,我们希望保留资源,以便我们可以调试问题。

请注意,我们将作业的backoffLimit指定为10;在 SQL Server pod 创建时间较长的情况下,我们需要这个值,这种情况下可能需要几分钟;如果我们不这样做,钩子将失败得太快。

  1. 最后一步是在图表的根目录中的values.yaml文件中提供默认模板值(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter12/03_voting-application-helm/values.yaml)。让我们来看看文件中的一些重要部分:
...
image:
  repository: packtpubkubernetesonwindows/voting-application
  pullPolicy: IfNotPresent
...
nodeSelector: 
  "kubernetes.io/os": windows
...
mssql-linux:
  acceptEula:
    value: "y"
  edition:
    value: Developer
  sapassword: "S3cur3P@ssw0rd"
  service:
    type: LoadBalancer
  persistence:
    enabled: true
    storageClass: azure-disk
  nodeSelector:
    "kubernetes.io/os": linux

您可以组织值;但是,它们已经方便地排列好了。例如,关于 Docker 镜像的所有内容都被分组到图像节点中,然后您可以在图表中引用图像存储库名称为{{ .Values.image.repository }}。要记住的一个非常重要的事情是提供适当的nodeSelector,以确保 pod 仅安排在 Windows 节点上。最后,使用其名称为依赖图表定义值。

在这里,我们使用了mssql-linux,因为这是我们在Chart.yaml文件中引用的图表。您可以在文档中阅读有关管理依赖项和定义值的更多信息helm.sh/docs/topics/charts/#chart-dependencies

Helm 的许多方面都基于惯例。您可以在文档中找到有关实施图表的最佳实践的更多信息helm.sh/docs/topics/chart_best_practices/。使用helm lint命令检查图表是否存在任何问题。

我们的投票应用程序的图表已准备就绪。现在,我们将在dev-helm命名空间中将此图表安装到我们的 Kubernetes 集群中:

  1. 在图表的root目录中打开 PowerShell 窗口。

  2. 确保从存储库中获取所有依赖的图表:

helm dependency update
  1. 执行 Helm 图表安装的dry run以检查清单文件:
helm install voting-application . `
 --namespace dev-helm `
 --debug `
 --dry-run

此命令将打印所有解析的清单文件,这些文件将应用于当前目录中图表的安装,使用默认值。

  1. 现在,安装图表。我们需要为安装提供扩展的超时,因为我们的 Entity Framework 数据库迁移作业可能需要几分钟才能成功。这取决于 SQL Server 初始化和准备连接的速度。使用以下命令:
helm install voting-application . `
 --namespace dev-helm `
 --debug `
 --timeout 900s
  1. 安装将需要一些时间;您可以在单独的 PowerShell 窗口中观察单个 Kubernetes 对象的部署进度。
kubectl get all -n dev-helm
  1. 安装结束后,使用kubectl get -n dev-helm svc -w voting-application获取我们的投票应用程序的 LoadBalancer 服务的外部 IP 地址。在 Web 浏览器中导航到该地址并享受!

在生产环境中,您应该使用 Helm 图表存储库来管理您的图表。您可以在v3.helm.sh/docs/topics/chart_repository/了解有关设置存储库的更多信息。此外,为了声明性地管理 Helm 图表的发布,类似于kubectl apply,您可以考虑使用Helmfilegithub.com/roboll/helmfile)。

在下一节中,您将学习如何轻松将 Azure Application Insights 添加到在 Windows 容器中运行的 ASP.NET MVC 应用程序中。我们还将向您展示如何执行刚刚安装的 Helm 发布的升级。

使用 Azure Application Insights 调试容器化应用程序

Azure Application Insights 是 Azure Monitor 的一部分,为您的应用程序提供应用程序性能管理APM)功能。它是一个庞大的平台,在 Azure 门户中具有丰富的用户界面UI),提供以下功能(以及其他功能):

  • 请求监控和跟踪,包括多个微服务之间的分布式跟踪

  • 异常监控和快照调试

  • 收集主机机器的性能计数器

  • 智能异常检测和警报

  • 轻松的日志收集和分析

对我们来说最有趣的功能是快照调试,它可以帮助诊断在不建议使用附加远程调试器的生产部署中的问题。为此,如果您想使用 Visual Studio 分析快照,您将需要 Visual Studio 2019 企业版。或者,您可以在 Azure 门户本身进行分析,该门户具有轻量级的基于 Web 的调试器。

或者,您可以使用 Istio 服务网格提供的带外仪器应用程序监控,在 Azure 上运行的 Kubernetes 应用程序,如docs.microsoft.com/en-us/azure/azure-monitor/app/kubernetes中所述。

启用 Azure Application Insights 与快照调试,我们需要按照以下步骤进行:

  1. 在 Visual Studio 项目中启用 Azure Application Insights。

  2. 安装Microsoft.ApplicationInsights.SnapshotCollector NuGet 包。

  3. 配置快照调试并修改 Serilog 配置以使用发送日志到System.Diagnostics.Trace的接收器。

  4. 添加演示异常。

  5. 构建一个新的 Docker 镜像并将其推送到 Docker Hub。

  6. 升级 Helm 发布。

之后,我们将能够直接在 Azure 门户中分析跟踪图、应用程序日志和异常。请注意,此日志收集解决方案与我们在第八章中演示的不同,部署混合 Azure Kubernetes 服务引擎集群,在那里我们使用 Azure Log Analytics 来处理 AKS Engine。它们使用相同的 Azure 服务,但在新解决方案中,我们将仅获取应用程序日志——在 Azure Log Analytics 视图中看不到 Kubernetes 或容器运行时日志。

启用 Azure 应用程序洞察

请按照以下步骤在我们的投票应用程序中启用 Azure 应用程序洞察。或者,您可以使用 Github 存储库中提供的现成源代码github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter12/04_voting-application-azure-application-insights-src

如果选择这样做,您需要在 Helm 发布升级期间的后续步骤中提供自己的 Azure 应用程序洞察密钥:

  1. 在 Visual Studio 2019 中打开VotingApplication解决方案。

  2. 在解决方案资源管理器中,右键单击VotingApplication项目,选择添加,然后选择应用程序洞察遥测...:

  1. 点击开始。

  2. 登录 Azure 并提供一个新的资源组和资源名称(或使用默认值)。

  3. 点击注册。该操作将需要几分钟的时间。一旦时间过去,将在您的 Azure 订阅中创建一个新的 Azure 应用程序洞察实例,并将适当的 NuGet 包添加到 Visual Studio 项目中。

  4. 更新 CodeLens 的资源并启用它来从System.Diagnostics中收集跟踪:

  1. 我们不希望将仪表键硬编码到 Docker 镜像中。应用程序洞察支持注入APPINSIGHTS_INSTRUMENTATIONKEY环境变量的能力。导航到解决方案中的ApplicationInsights.config文件,找到以下 XML 节点,记下密钥以供进一步使用,并删除 XML 节点:
<InstrumentationKey>4e810bf1-58c4-4af7-a67d-36fcdcf24a2f</InstrumentationKey>
  1. 搜索解决方案中所有的仪表键的出现。您会在_Layout.cshtml中找到另一个;用以下值替换它:
instrumentationKey: '@Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.Active.InstrumentationKey'
  1. 在解决方案资源管理器中右键单击VotingApplication项目,然后选择管理 Nuget Packages...。安装以下 NuGet 包Microsoft.ApplicationInsights.SnapshotCollectorandSerilog.Sinks.Trace

  2. 配置快照调试器。在ApplicationInsights.config文件中,确保在根节点ApplicationInsights中有以下 XML 节点:

<TelemetryProcessors>
 <Add Type="Microsoft.ApplicationInsights.SnapshotCollector.SnapshotCollectorTelemetryProcessor, Microsoft.ApplicationInsights.SnapshotCollector">
 <IsEnabled>true</IsEnabled>
 <IsEnabledInDeveloperMode>false</IsEnabledInDeveloperMode>
 <ThresholdForSnapshotting>1</ThresholdForSnapshotting>
 <MaximumSnapshotsRequired>3</MaximumSnapshotsRequired>
 <MaximumCollectionPlanSize>50</MaximumCollectionPlanSize>
 <ReconnectInterval>00:15:00</ReconnectInterval>
 <ProblemCounterResetInterval>1.00:00:00</ProblemCounterResetInterval>
 <SnapshotsPerTenMinutesLimit>3</SnapshotsPerTenMinutesLimit>
 <SnapshotsPerDayLimit>30</SnapshotsPerDayLimit>
 <SnapshotInLowPriorityThread>true</SnapshotInLowPriorityThread>
 <ProvideAnonymousTelemetry>false</ProvideAnonymousTelemetry>
 <FailedRequestLimit>3</FailedRequestLimit>
 </Add>
</TelemetryProcessors>
  1. NinjectWebCommon.cs文件的RegisterServices方法中注册 Serilog sink。您的日志记录器配置应如下所示:
Log.Logger = new LoggerConfiguration()
                 .ReadFrom.AppSettings()
                 .Enrich.FromLogContext()
                 .WriteTo.EventLog(source: "VotingApplication", logName: "VotingApplication", manageEventSource: false)
                 .WriteTo.Trace()
                 .CreateLogger();
  1. HomeController.cs文件中,添加一个新的控制器动作TestException,我们将用于测试快照调试。它应该只是抛出一个未处理的异常:
public ActionResult TestException()
{
    throw new InvalidOperationException("This action always throws an exception!");
}

此时,我们的投票应用程序已完全配置为使用 Azure Application Insights。现在可以使用以下步骤升级 Helm 发布:

  1. 使用1.5.0标签构建一个新的 Docker 镜像,就像我们在之前的章节中所做的那样,并将其推送到 Docker Hub。在我们的情况下,它将被称为packtpubkubernetesonwindows/voting-application:1.5.0

  2. 导航到应用程序的 Helm 图表所在的目录。

  3. Chart.yaml文件中,使用1.5.0(与 Docker 镜像标签相同)作为appVersion。根据我们的最佳实践建议,更改图表的版本,例如使用0.2.0

  4. values.yaml文件中,添加您的仪表键,并将replicaCount增加到5

azureApplicationInsightsKey: 4e810bf1-58c4-4af7-a67d-36fcdcf24a2f
replicaCount: 5
  1. 现在,我们需要将仪表键注入到Voting应用程序的Deployment中的 pod 模板中。修改templates\deployment.yaml,以便将azureApplicationInsightsKey注入到APPINSIGHTS_INSTRUMENTATIONKEY环境变量中:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "voting-application.fullname" . }}
  ...
spec:
  ...
  template:
    ...
    spec:
      ...
      containers:
        - name: {{ .Chart.Name }}-frontend
          ...
          env:
          - name: APPINSIGHTS_INSTRUMENTATIONKEY
            value: {{ .Values.azureApplicationInsightsKey }}
          ...
  1. 使用图表的新版本执行 Helm 发布的dry run
helm upgrade voting-application . `
 --namespace dev-helm `
 --debug `
 --dry-run
  1. 运行upgrade
helm upgrade voting-application . `
 --namespace dev-helm `
 --debug `
 --timeout 900s
  1. 等待所有副本升级到新版本。

现在,您的应用程序应该正在运行并将所有遥测发送到 Azure Application Insights。您可以从 Azure 门户导航到 Application Insights(portal.azure.com/),或者通过在 Visual Studio 中右键单击Connected Services下的Application Insights并选择Open Application Insights Portal来直接打开它:

您可以探索当前配置中提供的多个开箱即用的功能,例如将遥测数据可视化为应用程序地图,显示应用程序中不同组件之间的依赖关系及其当前状态:

如果您对最终用户请求的整体性能感兴趣,可以查看基于 ASP.NET MVC 遥测的专用仪表板:

当然,您还可以检查由 Serilog 汇集的应用程序日志。此视图中最重要的功能是使用 Kusto 语言(docs.microsoft.com/en-us/azure/kusto/query/)运行复杂查询的可能性,该语言专为分析日志数据而设计:

您可以在官方文档中了解有关 Azure 应用程序洞察功能的更多信息docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview

现在,让我们看看如何使用快照调试器来调试您的容器化应用程序,即使您没有访问远程调试器也可以。

快照调试器

Azure 应用程序洞察服务提供了快照调试器,这是一个用于监视应用程序异常遥测的功能,包括生产场景。每当出现未处理的异常(顶部抛出),快照调试器都会收集可以直接在 Azure 门户中分析的托管内存转储,或者针对更高级的场景,使用 Visual Studio 2019 企业版。如果您在安装程序中选择了 ASP.NET 工作负载,则 Visual Studio 将默认安装此功能。

快照调试可以为不使用 ASP.NET MVC 的常规.NET 应用程序进行配置。您可以在文档中了解更多信息docs.microsoft.com/en-us/azure/azure-monitor/app/snapshot-debugger-vm#configure-snapshot-collection-for-other-net-applications

在前面的段落中,我们已经通过安装Microsoft.ApplicationInsights.SnapshotCollector NuGet 包并提供额外的配置来启用了应用程序中的快照调试。现在,我们可以在我们的投票应用程序中测试此功能:

  1. 在您的网络浏览器中,导航到始终引发异常的测试端点:http://<serviceExternalIp>/Home/TestException。触发此端点两次;默认情况下,我们必须多次触发相同的异常才能触发快照收集。

  2. 您将看到我们投票应用程序的默认错误页面。与此同时,快照已经被收集,对最终用户几乎没有性能影响。

  3. 在 Azure 门户中为我们的投票应用程序导航到应用程序洞察。

  4. 打开“失败”窗格,并在查看“操作”选项卡时选择“操作”按钮,或在查看“异常”选项卡时选择“异常”按钮:

  1. 从右侧选择示例操作窗格并打开异常发生之一。

  2. 一开始,您在时间轴上看不到任何快照;您必须首先添加应用程序洞察快照调试器角色。要做到这一点,请单击(看不到快照?排除故障):

  1. 单击添加应用程序洞察快照调试器角色:

  1. 之后,将执行基本的健康检查。请记住,快照上传需要几分钟的时间,因此,如果您遇到任何健康检查失败,请在几分钟后重试。

  2. 现在,在端到端事务详细信息视图中,您将看到代表调试快照的小图标。单击其中一个:

  1. 调试快照视图为您提供了轻量级的调试器功能,包括代码反编译。要在 Visual Studio 2019 Enterprise 中分析快照,请单击“下载快照”按钮:

  1. 文件下载完成后,双击在 Visual Studio 中打开它。

  2. 在 Visual Studio 中,根据您的需求,单击“仅使用托管调试”或“调试托管内存”。当您分析内存泄漏和其他与内存相关的问题时,第二个选项非常有用。

  3. 您可能需要选择源代码位置,以便查看源代码视图(docs.microsoft.com/en-us/visualstudio/debugger/specify-symbol-dot-pdb-and-source-files-in-the-visual-studio-debugger?view=vs-2019)。

  4. 现在,您可以使用您一直使用的所有调试工具,例如,您可以分析并行堆栈视图:

如果在使用快照调试器时遇到任何其他问题,请查看官方的故障排除指南docs.microsoft.com/en-us/azure/azure-monitor/app/snapshot-debugger-troubleshoot

快照调试器甚至具有更多功能,可以设置实时快照点,以便您可以在不等待异常的情况下创建快照。不幸的是,目前此功能仅适用于在 Linux 容器中运行应用程序的 Azure 应用服务工作负载或托管的 AKS 集群。您可以在文档中找到更多信息docs.microsoft.com/en-us/visualstudio/debugger/debug-live-azure-applications?view=vs-2019

在下一节中,我们将介绍 Kubernetes 仪表板。

使用 Kubernetes 仪表板

Kubernetes 仪表板(github.com/kubernetes/dashboard)是默认的基于 Web 的用户界面,用于部署、管理和排除运行在 Kubernetes 上的应用程序。通常建议您使用声明性的 kubectl 管理集群,而不是使用仪表板,但它仍然是一个有用的工具,可以查看集群概述,分析日志,并快速执行到 pod 容器中。

要使用仪表板,您必须首先安装它。您有以下选项可以这样做:

  • 通过运行kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta6/aio/deploy/recommended.yaml来使用官方清单进行部署。您可以在文档中的kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/中再次检查最新版本。

  • 使用helm install kubernetes-dashboard stable/kubernetes-dashboard命令安装 Helm 图表。

  • 在 AKS Engine 中,使用默认启用的kubernetes-dashboard附加组件。

重要的是要知道 Kubernetes API 和 Kubernetes 仪表板有严格的兼容性规则。 您可以在官方发布页面github.com/kubernetes/dashboard/releases上检查矩阵。 目前,AKS Engine 部署了版本1.10.1的仪表板,与 Kubernetes API 的最新版本不兼容。 这意味着我们将使用官方清单部署仪表板。 AKS Engine 集群默认为启用 RBAC 的集群,因此我们需要配置 RBAC 以便作为集群管理员使用仪表板。

部署 Kubernetes 仪表板

要部署和配置 RBAC,请按照以下步骤进行:

  1. 打开 PowerShell 窗口。

  2. 使用官方清单部署 Kubernetes 仪表板:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta6/aio/deploy/recommended.yaml
  1. admin-user创建serviceaccount.yaml清单文件:
apiVersion: v1
kind: ServiceAccount
metadata:
 name: admin-user
 namespace: kubernetes-dashboard
  1. 使用kubectl apply -f serviceaccount.yaml命令应用清单文件。

  2. 创建clusterrolebinding.yaml清单文件,为此用户授予cluster-admin角色:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
  1. 使用kubectl apply -f clusterrolebinding.yaml命令应用清单文件。

  2. 要获取此用户的令牌,请在 PowerShell 中使用以下代码段,并复制token:后面的值:

kubectl -n kubernetes-dashboard describe secrets ((kubectl -n kubernetes-dashboard get secrets | Select-String "admin-user-token") -Split "\s+")[0]

在授予将用于访问仪表板的 ServiceAccount 的cluster-admin角色时,您需要了解任何安全影响。 拥有admin-user ServiceAccount 令牌的任何人都将能够在您的集群中执行任何操作。 在生产场景中,考虑创建仅公开必要功能的角色。

现在,您可以访问仪表板。 要做到这一点,请按照以下步骤进行:

  1. 在 PowerShell 窗口中,使用kubectl proxy命令启动连接到 API 的代理。 仪表板未公开为外部服务,这意味着我们必须使用代理。

  2. 打开 Web 浏览器,转到http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

  3. 使用令牌选项进行身份验证,并提供我们在前面步骤中检索到的令牌。

  4. 您将被重定向到集群的概述:

Kubernetes 仪表板具有多个功能,涵盖了kubectl提供的许多功能。在接下来的部分中,我们将探讨如何访问容器日志并执行到 Pod 容器中,因为它们在调试场景中非常有用。

访问 Pod 容器日志

Kubernetes 仪表板为您提供了一个方便的界面,可以快速访问 pod 容器日志。要访问我们投票应用程序的一个 pod 的日志,请按照以下步骤进行:

  1. 在菜单中,导航到工作负载 | Pod。

  2. 找到我们投票应用程序的一个 pod。在右侧,点击三个点按钮,然后选择日志。

  3. 您将被重定向到日志视图,在那里您可以实时检查日志,就像使用kubectl logs命令一样:

接下来,让我们看看如何exec到一个 Pod 容器中。

在 Pod 容器中执行命令

与访问日志类似,您可以exec到 Pod 容器中以运行临时命令。在调试问题或快速引入开发集群中的配置更改时,这种方法非常有用。执行以下步骤:

  1. 在菜单中,导航到工作负载 | Pod。

  2. 找到我们投票应用程序的一个 pod。在右侧,点击三个点按钮,然后选择e``xec

  3. 几秒钟后,PowerShell 终端将打开。您可以运行任意的 PowerShell 命令并修改容器状态:

恭喜!您已成功部署了 Kubernetes 仪表板,现在又多了一个有用的调试工具。在下一节中,您将学习如何使用 Azure Dev Spaces 改进 Kubernetes 的开发环境。

在团队中使用 Azure Dev Spaces 进行微服务开发

Azure Dev Spaces(docs.microsoft.com/en-us/azure/dev-spaces/),也称为AZDSAzure Dev Spaces的缩写),是微软提供的增强 Kubernetes 开发体验的最新产品之一。该服务为使用 AKS 集群的团队提供了快速和迭代的开发体验。请注意,目前仅支持托管的 AKS 集群,这意味着您无法为此服务使用 AKS Engine。此外,当前版本不支持开发 Windows 容器应用程序;可以与现有的 Windows pod 进行交互,但它们不会由 AZDS 管理(docs.microsoft.com/en-us/azure/dev-spaces/how-to/run-dev-spaces-windows-containers)。从这个角度来看,AZDS 对于 Windows 容器应用程序开发并不有用,但由于很可能很快会得到支持,我们将为您概述这一产品。

AZDS 的主要特点如下:

  • 您可以最小化本地开发环境设置。您可以在 AKS 中直接调试和测试分布式应用程序的所有组件,而无需替换或模拟依赖关系(开发/生产一致性)。

  • 您可以将 Kubernetes 集群组织成共享和私有的 Dev Spaces。

  • 它可以独立更新微服务,而不影响 AKS 集群和其他开发人员的其余部分。您可以开发自己的服务版本,在隔离环境中进行测试,并在准备好与其他团队成员共享时更新实例,以便所有人都能看到。

  • 它与 Visual Studio Code 和 Visual Studio 2019 完全集成,包括它们的远程调试功能。也可以从 Azure CLI 进行管理。

  • 它可以将您的本地计算机连接到 Kubernetes 集群,并测试或调试本地应用程序(带有或不带有容器),并使用所有依赖项(docs.microsoft.com/en-us/azure/dev-spaces/how-to/connect)。此功能类似于 telepresence。

  • 它通过增量代码编译在容器中提供更快的开发循环,每当检测到代码更改时。

要创建 AKS 集群,您可以使用我们在第四章中提供的 Powershell 脚本,Kubernetes 概念和 Windows 支持github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter04/05_CreateAKSWithWindowsNodes.ps1)。

该脚本还可以仅创建具有两个节点 Linux 池的集群。 按照以下步骤创建启用了 AZDS 的 AKS 集群:

  1. 下载脚本并使用适当的参数执行。 您需要选择支持 AZDS 的 Azure 位置(docs.microsoft.com/en-us/azure/dev-spaces/about#supported-regions-and-configurations)并选择该位置可用的 Kubernetes 版本(使用az aks get-versions --location <azureLocation>命令)。 在此示例中,我们将在westeurope位置创建一个名为devspaces-demo的 AKS 集群实例,并选择 Kubernetes 版本1.15.4。 请务必选择不包含保留字或商标的集群名称,否则您将无法启用 AZDS:
.\05_CreateAKSWithWindowsNodes.ps1 `
 -windowsPassword "S3cur3P@ssw0rd" `
 -azureLocation "westeurope" `
 -kubernetesVersion "1.15.4"
 -aksClusterName "devspaces-demo"
 -skipAddingWindowsNodePool $true
  1. 集群部署大约需要 15 分钟。 完成后,将添加并设置名为aks-windows-clusterkubectl的新上下文为默认值。

  2. 使用以下命令为集群启用 AZDS:

az aks use-dev-spaces `
 --resource-group "aks-windows-resource-group" `
 --name "devspaces-demo"
  1. 将安装 AZDS CLI。 在提示时将default命名空间用作 Dev Space。

现在 AKS 集群已启用 AZDS,我们可以演示在 Visual Studio 2019 中创建新的 ASP.NET Core 3.0 Kubernetes 应用程序并直接在集群中进行调试有多么容易。 按照以下步骤创建应用程序:

  1. 打开 Visual Studio 2019 并选择创建新项目。

  2. 找到适用于 Kubernetes 的容器应用程序模板,然后单击“下一步”。

  3. 选择项目名称和位置,然后单击“下一步”。

  4. 选择 Web 应用程序(模型-视图-控制器)类型,然后单击“创建”。

  5. 我们需要对默认配置进行小的更改。 在charts\azds-demo\values.yaml文件中,确保使用以下代码启用ingress

ingress:
 enabled: true
  1. 默认情况下,Kestrel 监听端口5000。我们需要将端口更改为80,以便与 Dockerfile 和 Kubernetes Service 兼容。在Program.cs文件中,确保应用程序启动如下所示:
public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder
                    .UseUrls("http://0.0.0.0:80")
                    .UseStartup<Startup>();
            });

启用了 AZDS 支持的项目具有azds.yaml文件,其中定义了 Dev Spaces 配置、Dockerfile 和具有 Helm 图表的charts目录,准备好由 AZDS 部署到集群。现在,让我们将应用程序部署到我们的 AKS 集群中的default Dev Space 中:

  1. 从项目的启动设置中,选择 Azure Dev Spaces:

  1. 选择devspaces demo AKS clusterdefault space,并将其标记为公开访问,然后继续点击确定。

  2. AZDS 将构建 Dockerfile,安装 Helm 图表,并附加调试器。在 Web 浏览器中,Ingress 公共端点将自动打开,例如http://default.azds-demo.2dpkt6cj7f.weu.azds.io/

  3. HomeController.cs文件中,在索引控制器操作中添加断点。刷新浏览器中的网页,您将看到断点被捕获,就像应用程序在本地环境中进行调试一样!

  4. 停止调试并在Index.cshtml文件中引入更改。例如,将主标题更改为以下内容:

<h1 class="display-4">Welcome - Modified</h1>
  1. 再次使用 Azure Dev Spaces 配置启动应用程序。在输出窗口中,您将看到应用程序被快速重建,并且一段时间后,修改后的主页将再次在 Web 浏览器中打开:

要了解团队开发中更多的 AZDS 场景,请查看官方文档docs.microsoft.com/en-us/azure/dev-spaces/team-development-netcore-visualstudio

所有这些都发生在 AKS 集群上。正如您所看到的,与在正常的开发循环中构建 Docker 镜像、推送它并部署新的部署相比,开发迭代要快得多。

要删除 AKS 集群,请使用az group delete --name aks-windows-resource-group --yes命令。

恭喜!您已成功为您的 AKS 集群设置了 Azure Dev Spaces。

总结

本章重点介绍了作为开发人员如何通过 Kubernetes 集群改进开发体验。首先,我们学习了如何为 Visual Studio Code 和 Visual Studio 2019 配置必要的开发扩展。接下来,您学习了如何使用 Helm 打包 Kubernetes 应用程序,首先是通过使用 Microsoft SQL Server 的官方 Helm 图表,然后是通过为我们的投票应用程序创建一个专用图表。

接下来,我们学习了如何将 Azure Application Insights 集成到您的应用程序中,以及如何利用高级功能,如快照调试器,以便在 Windows pod 的生产场景中调试问题。使用我们新的带有 Application Insights 仪表的 Docker 镜像,我们学习了如何执行 Helm 发布升级。我们介绍了 Kubernetes Dashboard,这是最常用的 Kubernetes Web UI。最后,您了解了 Azure Dev Spaces 服务是什么,以及在使用 AKS 集群时如何使用它来增加开发迭代速度。

在下一章中,我们将专注于安全这一重要主题,特别是在 Windows 容器的背景下。

问题

  1. Helm 是什么,为什么应该使用它?

  2. Helm 版本二和三之间最大的区别是什么?

  3. 如何在 Helm 图表中实现自动的 Entity Framework 数据库迁移?

  4. 如何执行安装为 Helm 图表的应用程序的新版本的发布?

  5. 快照调试器是什么,如何在生产场景中使用它?

  6. 为什么不建议使用 Kubernetes Dashboard 来修改集群中的资源?

  7. 使用 Azure Dev Spaces 的优势是什么?

您可以在本书的评估部分找到这些问题的答案。

进一步阅读

第十三章:保护 Kubernetes 集群和应用程序

安全性这个话题值得特别关注——Kubernetes 是一个庞大而复杂的系统,在这个系统中安全性并不明显,潜在的攻击向量也不会立即显现。如果考虑到这个系统可以执行的强大操作以及它与操作系统内部的深度集成,那么在 Kubernetes 中考虑安全性就更加重要了。只是为了让您了解,如果您忽视了配置细节,事情可能会变得很糟糕,可以看一下有关特斯拉因为 Kubernetes Dashboard 的公共未经身份验证端点而被加密挖矿的文章blog.heptio.com/on-securing-the-kubernetes-dashboard-16b09b1b7aca

在本章中,我们将为您提供保护 Kubernetes 集群的一般最佳实践,包括 Windows 工作负载的视角。Windows 节点有一些限制——例如,只能直接从节点存储(而不是内存)中以明文形式挂载密钥,但它们在不同方面也比 Linux 节点更安全。

本章将涵盖以下主题:

  • 保护 Kubernetes 集群

  • 保护 Windows 上的容器运行时

  • 使用网络策略部署安全应用程序

  • Windows 机器上的 Kubernetes 密钥

技术要求

对于本章,您将需要以下内容:

  • 已安装的 Windows 10 Pro、企业版或教育版(1903 版本或更高版本,64 位)

  • Azure 账户

  • 使用 AKS Engine 部署的 Windows/Linux Kubernetes 集群

要跟着做,您需要自己的 Azure 账户,以便为 Kubernetes 集群创建 Azure 资源。如果您之前没有在前几章创建过账户,您可以阅读更多关于如何获取个人使用的有限免费账户的信息azure.microsoft.com/en-us/free/

使用 AKS Engine 部署 Kubernetes 集群已在第八章中介绍过,部署混合 Azure Kubernetes 服务引擎集群

您可以从官方 GitHub 存储库下载本书章节的最新代码示例github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter13

保护 Kubernetes 集群

在本节中,我们将提供一些关于如何保护 Kubernetes 集群的一般指导。此外,我们将探讨在 AKS Engine 集群中使用 Azure Active Directory(AAD)集成进行 API 用户身份验证的主题。本章提供的最佳实践清单并不详尽,因此请始终查阅最新的官方文档,以确保您遵循建议。

现在,让我们逐个讨论以下各小节中的一般建议清单。

使用内置 RBAC 进行授权

我们已经在第十一章中介绍了 Kubernetes 提供的基于角色的访问控制(RBAC)用于 API 授权。这种机制允许您配置细粒度的权限集,并将其分配给用户、组和服务帐户。通过这种方式,作为集群管理员,您可以控制集群用户(内部和外部)与 API 服务器的交互方式,他们可以访问哪些 API 资源以及可以执行哪些操作。同时,您应该使用命名空间来创建资源之间的第一个边界。这也使得应用 RBAC 策略更加容易。

对于 RBAC,使用最小特权原则,并倾向于将 RoleBindings 分配给组,而不是单个用户,以减少管理开销。如果使用外部身份验证提供者,您可以轻松地与提供者提供的组集成。在引导集群时,建议您同时使用 Node 和 RBAC 授权器(对 API 服务器使用--authorization-mode=Node,RBAC参数),结合 NodeRestriction 准入插件。这是 AKS Engine 初始化集群的默认方式。

使用外部身份验证提供者

所有 API 调用都必须经过身份验证。这对外部(普通)用户以及内部 Kubernetes 基础设施的成员(例如 kubelet)都是如此。在基础设施的情况下,这些用户通常使用带有令牌或 X509 客户端证书的 ServiceAccounts,这些证书是在引导集群时创建的。Kubernetes 本身不提供管理访问集群的普通外部用户的手段;这应该委托给一个可以与 Kubernetes 集成的外部身份验证提供者,例如通过认证代理。

您应选择适合您的组织并遵循用户的常见访问模式的身份验证机制。例如,如果您正在运行 AKS 引擎,很可能已经在 Azure 订阅中使用 Azure Active Directory 来管理用户和角色。除此之外,您应考虑使用组来使 RBAC 策略管理更加简单,并与 AAD 更加集成。

除了 AAD 之外,认证代理和认证 webhook 还可以让您有可能与不同的协议集成,例如 LDAP、SAML 或 Kerberos。

在本节的最后,我们将演示如何为您的 AKS 引擎集群启用 AAD 集成。

使用 kubeadm 引导集群

如果您手动部署集群,请使用 kubeadm,它可以安全地引导集群。它可以生成一个自签名的 CA 来为集群中的所有组件设置身份,生成用于加入新节点的令牌(TLS 引导),并提供证书管理功能(kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-certs/)。初始化一个安全的 Kubernetes 集群是 kubeadm 的首要设计重点(github.com/kubernetes/kubeadm/blob/master/docs/design/design_v1.10.md)。

禁用公共 API 访问

对于生产场景,仅使用 RBAC 授权进行 Kubernetes API 的适当用户身份验证可能不足够(从安全角度来看),如果 API 是公开访问的。为了减少 Kubernetes API 的攻击向量,考虑禁用公共 API 访问,以及不直接将任何 Kubernetes 节点暴露给互联网。当然,这将需要您使用 VPN 或 jumpbox 主机来访问 API,但这绝对更安全。

AKS 引擎可以通过对集群 apimodel 进行简单更改来为您配置此功能。您可以在官方文档中阅读有关禁用公共 API 访问的更多信息github.com/Azure/aks-engine/blob/master/docs/topics/features.md#private-cluster。或者,您可以考虑加固主 VM 的 NAT 入站规则,限制允许通过 HTTPS 和 SSH 连接到机器的 IP 范围。

禁用公共仪表板

与 Kubernetes API 类似,您应该禁用公开访问的 Kubernetes 仪表板。在常见的安装中,仪表板可能会暴露为一个 LoadBalancer 服务;在最坏的情况下,这将是一个具有cluster-admin角色的 ServiceAccount。推荐的做法是永远不要使用 LoadBalancer 服务公开 Kubernetes 仪表板,并始终使用kubectl proxy来访问页面。

此外,Kubernetes 仪表板的 ServiceAccount 应具有足够满足您用例的最低权限。您很可能永远不会在生产环境中使用 Kubernetes 仪表板来创建或编辑部署,那么为什么您需要对这些敏感的 API 资源具有写访问权限呢?

以非特权模式运行容器

在 Kubernetes 中,可以指定一个 pod 是否具有特权。特权 pod 可能包含以特权模式运行的容器,这基本上意味着容器可以访问主机上的所有设备,这与在主机上以 root(或管理员)权限运行的进程具有类似的权限。

确保您的 pod 容器在操作系统中以非特权模式运行是一个良好的做法;这遵循了最小权限原则。此外,您应该考虑使用 PodSecurityPolicy admission controller 来强制执行一组规则,一个 pod 必须满足才能被调度。一个示例的限制性策略可以在raw.githubusercontent.com/kubernetes/website/master/content/en/examples/policy/restricted-psp.yaml找到。

请注意,在 Windows 容器的情况下,不支持运行特权容器。此外,对于 Windows 工作负载,请考虑使用组管理服务帐户(gMSAs,kubernetes.io/docs/tasks/configure-pod-container/configure-gmsa/)。

加密数据的静态存储

数据在静止状态下加密被认为是一种普遍的良好做法(有时是法律强制要求)对于所有系统。在 Kubernetes 中,您需要确保 etcd 集群数据被加密。这将为您的 API 资源和秘密提供额外的安全层,否则这些信息将以未加密的形式保存在 etcd 中。在官方 Kubernetes 文档中,有关在静止状态下加密秘密的内容被单独讨论。

对于秘密,您应该始终使用延迟绑定,通过将秘密注入到 pod 中作为卷或环境变量。请注意,在 Linux 上将秘密注入为环境变量是不太安全的;当您拥有 root 权限时,您可以从/proc/<pid>/environ枚举出一个进程的所有环境变量。在 Windows 节点上,问题甚至更加复杂:您仍然可以访问进程的环境变量,但卷目前无法使用内存文件系统。这意味着秘密随后直接存储在节点磁盘上。这意味着您应该考虑加密您的 Windows 节点存储,以最小化凭据的暴露。我们将在接下来的章节中讨论这个问题。

使用网络策略

网络策略充当您的 pod 之间的防火墙,允许您控制容器化应用程序的网络访问。在 Kubernetes 集群中,默认情况下,pod 之间没有网络通信的限制——基本上,所有的流量都是可能的。使用一种宽松的网络策略模型是一种良好的做法,它默认拒绝所有流量,并只允许连接,如果已经定义了专用的网络策略。

您可以在官方文档中阅读更多关于支持 AKS Engine 上网络策略的网络提供商的信息。请注意,目前这些提供商不支持 Windows pod,除了 Tigera Essentials 订阅服务的企业版本提供的 Calico(https://www.tigera.io/media/pr-calico-for-windows)。

保护镜像供应链和扫描镜像

在第三章中,使用容器镜像,我们描述了如何使用Docker Content TrustDCT)对 Docker 镜像进行签名和验证。您应该在生产中考虑使用这种方法来进行 Docker 镜像流水线。此外,考虑整合开源工具,如Anchoregithub.com/anchore/anchore-engine)和Clairgithub.com/quay/clair),这些工具可以帮助您识别常见的漏洞和曝光(CVEs)并加以缓解。

旋转基础设施凭据和证书

一般来说,凭据或令牌的有效期越短,攻击者利用这种凭据的难度就越大。利用这一原则为在您的集群中使用的证书和令牌设置较短的生命周期,并在可能的情况下实施自动轮换。当您发现自己受到攻击时,这可以成为您的秘密武器;如果您能有效地轮换证书,您可以随时轮换它们,并使任何被截获的凭据无效。

对于 AKS 和 AKS Engine,考虑使用与Azure Key Vault集成,这将使您的秘密和证书管理和轮换变得更加容易。您可以在官方文档中阅读更多信息,网址为github.com/Azure/kubernetes-keyvault-flexvol

此外,考虑集成一个认证提供者,用于发放具有短期有效期的用户令牌。您可以使用这种方法来提供及时特权访问管理,这可以大大限制用户拥有资源的上帝模式访问的时间。

启用审计日志

审计日志应始终在生产集群中可用。这将使监视和警报设置对访问异常和意外 API 调用进行监控成为可能。您越早发现任何禁止的 API 响应,就越有可能及时做出反应,防止攻击者获取对集群的访问权限。您可以在官方文档中阅读有关 Kubernetes 审计的更多信息,网址为kubernetes.io/docs/tasks/debug-application-cluster/audit/

在设置生产集群时,请确保阅读官方 Kubernetes 指南以保护集群。您可以在kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/找到更多详细信息。

现在,在我们已经了解了 Kubernetes 集群的最重要的安全最佳实践之后,我们将看看如何在 AKS Engine 集群中启用Azure Active Directory(AAD)进行客户端身份验证。

集成 AAD 与 AKS Engine

AKS Engine 可以轻松与 AAD 集成,以提供 Kubernetes API 客户端身份验证。与 AAD 一起,这种方法可以用于为映射到 AAD 组的用户组创建 RoleBindings 和 ClusterRoleBindings。

让我们看看如何创建一个具有 AAD 集成的 AKS Engine 集群,并为集群管理员创建一个 AAD 组。这种方法可以扩展到管理多个具有不同 RBAC 绑定的 AAD 组。

不支持将 AAD 集成添加到现有的 AKS Engine 集群。因此,您需要在集群部署时做出这个决定。

为服务器和客户端配置 AAD 应用程序的步骤,以及创建管理员 AAD 组的步骤,已经作为 Powershell 脚本提供,方便您使用。您可以使用该脚本或按照以下步骤操作:

  1. 打开 PowerShell 窗口,并使用全局唯一的 DNS 前缀定义$dnsPrefix变量,稍后将用于 AKS Engine 部署,例如:
$dnsPrefix = "handson-aks-engine-win-aad"
  1. 创建一个将代表 Kubernetes API 服务器的 AAD 服务器应用程序,并将appId存储以供进一步使用作为$serverApplicationId变量:
$serverApplicationId = az ad app create `
 --display-name "${dnsPrefix}Server" `
 --identifier-uris "https://${dnsPrefix}Server" `
 --query appId -o tsv
  1. 更新此应用程序的组成员资格声明:
az ad app update `
 --id $serverApplicationId `
 --set groupMembershipClaims=All
  1. 创建一个将用于 Azure 平台身份验证的服务主体
az ad sp create `
 --id $serverApplicationId
  1. 获取服务主体的密钥并将其存储以供进一步使用作为$serverApplicationSecret变量:
$serverApplicationSecret = az ad sp credential reset `
 --name $serverApplicationId `
 --credential-description "AKSPassword" `
 --query password -o tsv
  1. 现在,为服务器应用程序添加权限以读取目录数据、登录和读取用户配置文件:
az ad app permission add `
 --id $serverApplicationId `
 --api 00000003-0000-0000-c000-000000000000 `
 --api-permissions e1fe6dd8-ba31-4d61-89e7-88639da4683d=Scope 06da0dbc-49e2-44d2-8312-53f166ab848a=Scope 7ab1d382-f21e-4acd-a863-ba3e13f7da61=Role
  1. 授予权限:
az ad app permission grant `
 --id $serverApplicationId `
 --api 00000003-0000-0000-c000-000000000000 
az ad app permission admin-consent `
 --id $serverApplicationId
  1. 接下来的步骤将类似,但将适用于代表 kubectl 的 AAD 客户端应用程序。创建该应用程序并将appId存储为$clientApplicationId变量以供进一步使用:
$clientApplicationId = az ad app create `
 --display-name "${dnsPrefix}Client" `
 --native-app `
 --reply-urls "https://${dnsPrefix}Client" `
 --query appId -o tsv

根据您的 AAD 租户配置,您可能需要额外的权限来创建服务主体。您可以在官方文档中阅读更多内容docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#required-permissions

  1. 为应用程序创建服务主体:
az ad sp create `
 --id $clientApplicationId
  1. 确定服务器应用程序的 OAuth2 ID 并将其存储为$oauth2PermissionId
$oauth2PermissionId = az ad app show 
 --id $serverApplicationId `
 --query "oauth2Permissions[0].id" -o tsv
  1. 使用 OAuth2 ID 允许客户端和服务器应用程序之间的身份验证流程:
az ad app permission add `
 --id $clientApplicationId `
 --api $serverApplicationId `
 --api-permissions $oauth2PermissionId=Scope

az ad app permission grant `
 --id $clientApplicationId `
 --api $serverApplicationId
  1. 为 AKS Engine 管理员创建名为AksEngineAdmins的 AAD 组,并将其 ID 存储为$adminGroupId变量:
$adminGroupId = az ad group create `
 --display-name AksEngineAdmins `
 --mail-nickname AksEngineAdmins `
 --query "objectId" -o tsv
  1. 我们想要将当前用户添加到这个组。首先,让我们检索用户的objectId并将其存储为$currentUserObjectId变量:
$currentUserObjectId = az ad signed-in-user show `
 --query "objectId" -o tsv
  1. 将用户添加到 AKS Engine 管理员组:
az ad group member add `
 --group AksEngineAdmins `
 --member-id $currentUserObjectId
  1. 确定当前订阅的 AAD 租户 ID 并将其存储为$tenantId变量:
$tenantId = az account show `
 --query "tenantId" -o tsv
  1. 基于前面的变量打印 JSON 对象,该对象将在 AKS Engine apimodel 中使用:
echo @"
"aadProfile": {
 "serverAppID": "$serverApplicationId",
 "clientAppID": "$clientApplicationId",
 "tenantID": "$tenantId",
 "adminGroupID": "$adminGroupId"
}
"@

我们已经准备好部署带有 AAD 集成的 AKS Engine。为此,我们将使用一个 PowerShell 脚本,几乎与我们在之前章节中使用的方式完全相同(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter13/02_aks-engine-aad/CreateAKSEngineClusterWithWindowsNodes.ps1),以及 apimodel 模板(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter13/02_aks-engine-aad/kubernetes-windows-template.json)。执行 AKS Engine 部署,按照以下步骤进行:

  1. 下载 PowerShell 脚本和 apimodel 模板。

  2. 在文件位置打开 PowerShell 窗口。

  3. kubernetes-windows-template.json文件中,用前面段落中的自己的值替换aadProfile

  4. 使用适当的参数执行脚本:

.\CreateAKSEngineClusterWithWindowsNodes.ps1 `
 -azureSubscriptionId <azureSubscriptionId> `
 -dnsPrefix <dnsPrefix> `
 -windowsPassword 'S3cur3P@ssw0rd' `
 -resourceGroupName "aks-engine-aad-windows-resource-group" `
 -azureLocation "westus"
  1. 几分钟后,脚本将执行kubectl get pods命令,并提示您在 Web 浏览器中进行身份验证

  1. 导航到 URL,提供代码并登录。之后,您将成功在 Kubernetes API 服务器中进行身份验证,并能够使用 kubectl。

  2. 要检查允许您访问的 ClusterRoleBinding 的定义,请执行以下命令:

PS C:\src> kubectl describe clusterrolebinding aad-default-admin-group
Name:         aad-default-admin-group
Labels:       addonmanager.kubernetes.io/mode=EnsureExists
 kubernetes.io/cluster-service=true
Annotations:  <none>
Role:
 Kind:  ClusterRole
 Name:  cluster-admin
Subjects:
 Kind   Name                                  Namespace
 ----   ----                                  ---------
 Group  18d047eb-83f9-4740-96be-59555e88138f

根据您的需求,您现在可以配置更多的 AAD 组,创建角色并为它们提供适当的 RoleBindings。在下一节中,我们将看看如何确保 Windows 容器运行时安全运行。

在 Windows 中保护容器运行时

在保护容器运行时方面,Windows 容器与 Linux 容器有些不同。对于 Windows 容器,操作系统使用一个Job对象(不要与 Kubernetes 的Job对象混淆!)每个容器一个,具有用于在给定容器中运行的所有进程的系统命名空间过滤器。这提供了与主机机器的逻辑隔离,无法禁用。您可以在第一章中阅读有关 Windows 容器架构的更多信息,创建容器

这一事实有一个后果:在 Windows 中,特权容器不可用,尽管在 Linux 中可用。此外,随着 Kubernetes 对 Hyper-V 容器的支持即将到来,您将能够进一步保护容器运行时并强制执行更好的隔离。

对于 Linux 容器,您可以考虑在 Pod 中使用securityContext以作为非特权用户运行(其 ID 与0不同):

apiVersion: v1
kind: Pod
metadata:
  name: secured-pod
spec:
  securityContext:
    runAsUser: 1000

此外,您可以强制执行 PodSecurityPolicies,在调度 Pod 之前由准入控制器进行验证。通过这种方式,例如,您可以确保给定命名空间中没有以特权模式运行的 Pod。您必须使用 RBAC 来正确配置策略访问。

AKS Engine 默认启用了 PodSecurityPolicy 准入控制器,并提供了特权和受限策略。

对于 Windows 容器,标准的securityContext不适用,因为它是用于 Linux 容器的。Windows 容器在securityContext内部有一个专门的对象,名为windowsOptions,它可以启用一些目前仍处于alpha状态的 Windows 特定功能:

在下一节中,您将了解更多关于网络策略以及它们如何用于在 Kubernetes 上部署更安全的应用程序。

使用网络策略部署安全应用程序

在 Kubernetes 中,您可以使用网络策略为应用程序部署提供更好的网络隔离粒度。它们由NetworkPolicy对象表示,定义了一组 Pod 如何相互通信以及一般网络端点——可以将它们视为 OSI 模型第 3 层的网络分割的基本防火墙。当然,它们并不是高级防火墙的替代品。

NetworkPolicy对象使用标签选择器来识别它们附加到的 Pod。同样,标签选择器和 IP CIDR 用于为这些 Pod 定义入口和出口规则的目标。只有当网络策略具有与给定 Pod 匹配的标签选择器时,才会使用给定的网络策略。如果没有与给定 Pod 匹配的网络策略,它可以接受任何流量。

网络策略支持

为了使用网络策略,您需要使用一个支持网络策略的网络提供商(用于安装 Pod 网络,如第五章中所述,Kubernetes 网络)。最受欢迎的有以下几种:

不幸的是,目前没有任何支持 Windows 节点的网络提供商,这意味着您只能在 Linux 集群中使用网络策略。唯一宣布即将支持 Windows 节点和网络策略的网络提供商是 Calico 的企业版本,作为Tigera Essentials订阅服务的一部分提供(www.tigera.io/media/pr-calico-for-windows)。您目前可以在私人预览版本中尝试此服务,包括 Windows 节点支持。请注意,如果您使用 AKS 或 AKS Engine,则仅限于与Azurekubenet网络 CNI 插件一起使用 Calico 或 Cilium。

有关 AKS Engine 配置网络策略支持的更多详细信息,请参阅官方文档github.com/Azure/aks-engine/tree/master/examples/networkpolicy。此外,对于托管的 AKS,您可以考虑使用高级网络功能,允许您配置自己的 VNet,定义 Azure 网络安全组,并提供将您的 Pod 连接到 VNet 的自动连接功能-您可以在官方文档中阅读更多docs.microsoft.com/en-us/azure/aks/configure-azure-cni

让我们看看如何使用网络策略来强制执行集群中的 Pod 隔离。

配置网络策略

从安全的角度来看,网络策略很重要,因为默认情况下,Kubernetes 允许集群中的所有对所有通信。命名空间只提供了简单的隔离,仍然允许 pod 通过 IP 地址相互通信。在较大的集群或多租户场景中,你必须提供更好的网络隔离。尽管 Windows 节点目前还不支持网络策略(但最终支持),我们认为让你了解如何使用原生 Kubernetes 构建来实现网络分割是很重要的。

如果你有一个使用 Calico 网络的 AKS Engine Linux 集群,并且使用 Azure CNI 插件,你可以跟着配置你的 pod 的网络策略。使用这样的配置部署 AKS Engine 只需要对集群 apimodel 进行简单的更改,即在properties.orchestratorProfile中添加以下属性:

"kubernetesConfig": {
    "networkPolicy": "calico",
    "networkPlugin": "azure"
}

现在,我们将创建一个网络策略,阻止所有进入default命名空间中所有 pod 的流量。这与集群默认情况相反——命名空间中的 pod 将无法相互通信,除非你明确允许。之后,我们将部署一个简单的 Nginx web 服务器,后面是一个负载均衡器服务,并尝试从集群中的不同 pod 内部和 Azure 负载均衡器外部进行通信。然后,我们将创建一个网络策略,作为白名单规则,只针对 web 服务器的 TCP 端口 80。请按照以下步骤创建默认拒绝所有规则并部署 Nginx web 服务器:

  1. NetworkPolicy对象在default命名空间中拒绝所有进入流量创建一个名为default-deny-all-ingress.yaml的清单文件:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  namespace: default
  name: default-deny-all-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress

这是通过使用空的podSelector来实现的,它将选择所有的 pod。

  1. 使用kubectl apply -f .\default-deny-all-ingress.yaml命令应用清单文件。

  2. 如果你想更好地理解任何网络策略的影响,可以使用以下命令:

kubectl describe networkpolicy default-deny-all-ingress
  1. 为 Nginx 部署创建一个名为nginx-deployment.yaml的简单清单文件:
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
  1. 使用kubectl apply -f .\nginx-deployment.yaml命令应用清单文件。

  2. 为部署的负载均衡器服务创建一个名为nginx-service.yaml的清单文件:

apiVersion: v1
kind: Service
metadata:
  namespace: default
  name: nginx-service
  labels:
    app: nginx
spec:
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
  selector:
    app: nginx
  1. 使用kubectl apply -f .\nginx-service.yaml命令应用清单文件。

在定义非常严格的出口规则时要小心。使用拒绝所有出口规则,您将阻止 pod 访问 Kubernetes DNS 服务。

通过部署我们的 Nginx web 服务器和在default命名空间中拒绝所有入口流量到 pod 的默认规则,我们可以测试与 web 服务器的连接。

  1. 等待服务的外部 IP 出现,使用kubectl get svc -w命令,并在网络浏览器中打开该地址。您会看到连接挂起并最终超时,这是预期的。

  2. 让我们使用交互模式中运行 Bourne shell 的busybox pod 来检查这一点:

kubectl run --generator=run-pod/v1 busybox-debug -i --tty --image=busybox --rm --restart=Never -- sh
  1. 在 pod 中的 shell 会话中,尝试获取 Nginx 托管的网页。您可以使用服务的 DNS 名称和其中一个 pod 的 IP。在这两种情况下都会失败:
wget http://nginx-service:80
wget http://10.240.0.30:80

现在,让我们创建一个网络策略,允许 TCP 端口80上的入口流量到 Nginx pod。之后,您将能够从集群中的两个 pod 以及 Azure 负载均衡器进行通信。要配置策略,请按照以下步骤进行:

  1. busybox交互会话保持运行,并打开一个新的 PowerShell 窗口。

  2. 创建一个名为default-nginx-allow-ingress.yaml的清单文件,允许 TCP 端口80上的入口流量到所有带有标签app=nginx的 pod,来自所有来源:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  namespace: default
  name: default-nginx-allow-ingress
spec:
  podSelector:
    matchLabels:
      app: nginx
  ingress:
  - from: []
    ports:
    - protocol: TCP
      port: 80
  1. 使用kubectl apply -f .\default-nginx-allow-ingress.yaml命令应用清单文件。

  2. 在您的网络浏览器中,再次导航到服务的外部 IP。现在,您应该能够毫无问题地访问网页!

  3. 类似地,在busybox pod 容器中使用wget尝试相同的操作。您也将能够访问网页。

  4. 作为练习,为了证明端口过滤正常工作,您可以修改网络策略以使用不同的端口,或者在 TCP 端口上运行不同于80的 Nginx。

恭喜!您已成功使用网络策略在 Kubernetes 集群中配置了宽松的网络规则。从permissive网络策略模型开始是一个很好的做法,您可以拒绝所有入口流量到您的 pod(有时也是所有出口流量从 pod),并通过特定的网络策略允许连接。请注意,为此,您应该以可预测的方式组织网络策略,使用命名约定。这将使您的网络规则管理变得更加容易。

在接下来的部分中,我们将探讨如何在 Windows 机器上处理 Kubernetes secrets。

Windows 机器上的 Kubernetes secrets

在第四章中,Kubernetes 概念和 Windows 支持,我们提到 Windows 节点支持的限制之一是,挂载到 pod 的 Kubernetes secrets 作为卷写入节点磁盘存储(而不是 RAM 内存)时是明文。原因是 Windows 目前不支持将内存文件系统挂载到 pod 容器。这可能带来安全风险,并需要额外的操作来保护集群。同时,将 secrets 作为环境变量挂载也有其自己的安全风险——如果有系统访问权限,可以枚举进程的环境变量。在可以从内存文件系统挂载 secrets 作为卷之前,除了使用 Azure Key Vault 等第三方提供者,没有完全安全的解决方案来为 Windows 容器注入 secrets。

在 Kubernetes etcd 集群中at rest加密 secrets 是一个不同且重要的主题,在官方文档中有介绍:kubernetes.io/docs/tasks/administer-cluster/encrypt-data/

让我们进行一个小实验,以更好地理解问题和可能的影响。您将需要在前几章中使用的带有 Windows 节点的 AKS 引擎集群。请按照以下步骤进行:

  1. 打开 PowerShell 窗口,并创建一个包含用户名admin和密码Password123的 Base64 编码的secret-example.yaml清单文件:
apiVersion: v1
kind: Secret
metadata:
  name: secret-example
type: Opaque
data:
  username: YWRtaW4=
  password: UGFzc3dvcmQxMjM=
  1. 使用kubectl apply -f .\secret-example.yaml命令应用清单文件。

  2. 创建windows-example-deployment.yaml清单文件,用于部署在 Windows 上运行的示例 ASP.NET 应用程序,并在 pod 的C:\SecretExample目录中挂载secret-example secret:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: windows-example
  labels:
    app: sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: windows-example
  template:
    metadata:
      name: windows-example
      labels:
        app: windows-example
    spec:
      nodeSelector:
        "beta.kubernetes.io/os": windows
      containers:
      - name: windows-example
        image: mcr.microsoft.com/dotnet/core/samples:aspnetapp-nanoserver-1809
        ports:
          - containerPort: 80
        volumeMounts:
        - name: secret-example-volume
          mountPath: C:\SecretExample
          readOnly: true
      volumes:
      - name: secret-example-volume
        secret:
          secretName: secret-example
  1. 使用kubectl apply -f .\windows-example-deployment.yaml命令应用清单文件。

  2. 使用kubectl get pods -o wide命令确定运行 pod 的 Windows 节点。在我们的情况下,是2972k8s011

  3. 请按照第八章中的部署混合 Azure Kubernetes 服务引擎集群的子章节连接到虚拟机中的说明,创建到节点2972k8s011的远程桌面连接。

  4. 当命令行提示初始化时,使用docker ps命令来识别运行我们应用程序的 Docker 容器的 ID。接下来,运行docker inspect -f {{.Mounts}} <containerID>命令来获取 Docker 卷数据在主机磁盘存储上的物理位置:

  1. 现在,只需使用这个路径,检查目录内容,并使用type <filePath>命令来显示与我们秘密对象中password键对应的文件的内容:

正如您所看到的,我们已经毫无问题地检索到了Password123的值。在使用内存中的tmpfs文件系统将卷挂载到秘密中的 Linux 系统上,这并不容易!

这种当前设计存在明显的安全问题:任何能够访问节点磁盘存储数据的人都可以获取您的秘密(当前使用的)明文。这不仅涉及到对机器本身(物理或远程)的访问,还涉及到存储 Docker 卷的磁盘的备份

为了在一定程度上缓解这个问题,您应该为 Windows 节点磁盘使用磁盘加密。在内部部署的场景中,您可以考虑使用 BitLocker,在 Windows 服务器操作系统上有原生支持;您可以在官方文档中找到更多详细信息docs.microsoft.com/en-us/windows/security/information-protection/bitlocker/bitlocker-how-to-deploy-on-windows-server

对于 Azure 部署来说,好消息是 Azure VM 磁盘始终在 Azure 数据中心静态加密。如果您的场景要求您在 VM 操作系统级别提供加密,那么对于托管的 AKS 来说,这个功能目前还不支持(github.com/Azure/AKS/issues/629),而对于 AKS Engine,节点 VM 默认情况下是没有加密的(您无法在集群 apimodel 中控制它们),但您可以手动启用它。您可以在官方文档中阅读有关 Windows VM 加密方案的更多信息docs.microsoft.com/en-us/azure/virtual-machines/windows/disk-encryption-windows

为了演示如何手动加密 Windows 节点磁盘,让我们为我们集群中的一个节点2972k8s011打开加密:

  1. 打开 PowerShell 窗口并创建一个名为AksEngineEncryptionVault的 Azure 密钥保管库:
az keyvault create `
 --resource-group "aks-engine-windows-resource-group" `
 --name "AksEngineEncryptionVault" `
 --location "westeurope"
  1. 启用密钥保管库用于 Azure VM 的磁盘加密:
az keyvault update `
 --resource-group "aks-engine-windows-resource-group" `
 --name "AksEngineEncryptionVault" `
 --enabled-for-disk-encryption "true"
  1. All挂载到 VM 的2972k8s011节点启用磁盘加密:
az vm encryption enable `
 --resource-group "aks-engine-windows-resource-group" `
 --name "2972k8s011" `
 --disk-encryption-keyvault "AksEngineEncryptionVault" `
 --volume-type All
  1. 加密过程完成后,检查加密功能的当前状态:
PS C:\src> az vm encryption show `
>>            --resource-group "aks-engine-windows-resource-group" `
>>            --name "2972k8s011"
{
 "disks": [
 {
 ...
 "name": "2972k8s011_OsDisk_1_1986c424c52c46a39192cdc68c9b9cb9",
 "statuses": [
 {
 "code": "EncryptionState/encrypted",
 "displayStatus": "Encryption is enabled on disk",
 "level": "Info",
 "message": null,
 "time": null
 }
 ]
 }
 ]
}

这个过程必须重复进行,对集群中的所有 Windows 节点进行重复,并且在扩展集群时也必须重复进行。

恭喜!您已成功加密了 Windows 节点磁盘,以增加 Kubernetes 密钥安全性。

总结

本章主要关注了 Kubernetes 安全性。我们为您提供了 11 条保护 Kubernetes 集群的建议和最佳实践,从使用 RBAC 和集成外部身份验证提供程序,如 Azure Active Directory,到禁用 Kubernetes API 和仪表板的公共访问以及启用审计日志记录。我们演示了如何在 AKS Engine 集群上使用 Azure Active Directory 集成来简化 RBAC 管理和身份验证。接下来,我们讨论了如何在 Kubernetes 中保护容器运行时以及网络策略的作用(目前尚不支持在 Windows 节点上)。

最后,您了解了在 Linux 和 Windows 机器上注入 Kubernetes 密钥的区别,并且看到了,根据当前设计,访问 Windows 机器上的密钥更容易,可能会导致安全问题。为了缓解这一问题,我们向您展示了如何为在集群中用作 Windows 节点的 Azure VM 加密磁盘。

在下一章中,我们将重点讨论如何监视 Kubernetes 集群,特别是运行在 Windows 节点上的.NET 应用程序。

问题

  1. 为什么应该在 Kubernetes 中使用外部身份验证提供程序,比如 AAD?

  2. 禁用对 Kubernetes 仪表板的公共访问为什么重要?

  3. 为什么建议对 etcd 数据存储进行加密?

  4. 您可以在 Windows 机器上运行特权容器吗?

  5. Kubernetes 中的网络策略是什么,启用它们的先决条件是什么?

  6. Linux 和 Windows 节点在挂载密钥作为卷时的主要区别是什么?

  7. 为什么将密钥作为环境变量注入被认为比在 Linux 节点上使用卷不安全?

您可以在本书的评估中找到这些问题的答案。

进一步阅读

第十四章:使用 Prometheus 监控 Kubernetes 应用程序

作为容器编排器的 Kubernetes 是一个复杂的分布式系统,需要监控和警报才能在规模上正常运行。同时,您需要以相同的方式监控在 Kubernetes 上运行的应用程序——如果没有监控和警报,您就不知道应用程序的行为如何,是否发生任何故障,或者是否应该扩展工作负载。事实上,与监控和警报相关的挑战是企业采用 Kubernetes 时最常报告的阻碍之一。

幸运的是,多年来,市场上涌现出了多种日志聚合、遥测收集、警报甚至专门的应用性能管理APM)系统的解决方案。我们可以选择不同的软件即服务(SaaS)解决方案或开源系统,这些系统可以在本地托管,专门用于 Kubernetes 集群!

但是另一面是:我们受限于可以支持 Windows 容器和基于 Windows 的 Kubernetes 节点的解决方案。在 Kubernetes 中对 Windows 的生产级支持是非常近期的,没有可以立即使用的一揽子解决方案。因此,本章旨在概述 Kubernetes 的可用监控解决方案,并探讨如何实现支持 Windows 节点的自己的解决方案。

在本章中,我们将涵盖以下主题:

  • 可用的监控解决方案

  • 提供可观察的 Windows 节点

  • 使用 Helm 图表部署 Prometheus

  • Windows 性能计数器

  • 使用prometheus-net监控.NET 应用程序

  • 在 Grafana 中配置仪表板和警报

技术要求

本章,您将需要以下内容:

  • 安装了 Windows 10 Pro、企业版或教育版(1903 版或更高版本,64 位)

  • Microsoft Visual Studio 2019 Community(或任何其他版本),如果您想编辑应用程序的源代码并进行调试——Visual Studio Code 对经典.NET Framework 的支持有限

  • 已安装 Helm

  • Azure 账户

  • 使用 AKS Engine 部署的 Windows/Linux Kubernetes 集群,准备部署上一章中的投票应用程序

要跟进,您将需要自己的 Azure 帐户来为 Kubernetes 集群创建 Azure 资源。如果您还没有为之前的章节创建帐户,您可以在此处阅读有关如何获取用于个人使用的有限免费帐户的更多信息:azure.microsoft.com/en-us/free/

使用 AKS Engine 部署 Kubernetes 集群已在第八章中进行了介绍,部署混合 Azure Kubernetes 服务引擎集群。将投票应用程序部署到 Kubernetes 已在第十章中进行了介绍,部署 Microsoft SQL Server 2019 和 ASP.NET MVC 应用程序

您可以从官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter14

可用的监控解决方案

单词“监控”通常被用作一个涵盖以下内容的总称:

  • **可观察性:**为您的组件提供可观察性意味着公开有关其内部状态的信息,以便您可以轻松访问数据并对组件的实际状态进行推理。换句话说,如果某物是可观察的,您就可以理解它。提供可观察性的一个众所周知的特性示例是日志记录。您的应用程序生成日志,以便您可以检查应用程序的流程和当前状态。可观察性有三个支柱:日志记录、分布式跟踪和指标。分布式跟踪提供了对请求流经多个服务的洞察,例如使用关联 ID。指标可以是应用程序公开的数字信息,例如计数器或量规。

  • **监控:**这意味着收集组件的可观察数据并存储它,以便进行分析。

  • **分析和警报:**基于收集的监控数据,您可以进行分析,当组件被视为不健康时创建规则,并为您的团队配置警报。更复杂的情况涉及异常检测和机器学习。

在 Kubernetes 中,监控比监控单个应用程序还要复杂。通常,您可以将 Kubernetes 集群的监控划分为以下几个独立的领域:

  • 监控 Kubernetes 节点的硬件和操作系统基础设施

  • 监控容器运行时

  • 监控 Kubernetes 组件和资源本身

  • 监控在集群中运行的容器化应用程序

最后,您可以从托管解决方案与 Kubernetes 相关的角度来查看监控系统:

  • 本地监控:使用自己的云或裸金属基础设施,您可以为运行监控工具提供单独的集群,或者使用与应用程序相同的集群。第二种解决方案更容易,但只能考虑用于小型 Kubernetes 集群。您希望分开应用程序和监控工作负载;您特别不希望监控对应用程序的性能产生负面影响。这种方法的一个示例是部署自己的 Prometheus (prometheus.io/)实例来收集 Kubernetes 集群中的指标,以及日志分析解决方案,例如Elasticsearch, Logstash, Kibana (ELK) stack (www.elastic.co/what-is/elk-stack)。

  • 内部 SaaS 监控:如果您在云中运行,可以使用云服务提供商提供的 SaaS 产品,例如在 Azure 上,您可以使用 Azure Monitor (azure.microsoft.com/en-us/services/monitor/)。这些解决方案通常很容易与其他托管服务集成,例如 AKS。此外,对于日志监控,您可以利用 Azure Monitor 中的 Log Analytics (docs.microsoft.com/en-us/azure/azure-monitor/log-query/get-started-portal)。

  • 外部 SaaS 监控:在这种情况下,您可以使用外部公司提供的专用通用 SaaS 产品来监控在任何云中甚至本地运行的集群。监控平台的市场很大,其中一些知名的例子是 New Relic (newrelic.com/platform)和 Dynatrace (www.dynatrace.com/technologies/kubernetes-monitoring/)。

通常,使用内部 SaaS 监控比使用外部 SaaS 更便宜,但您面临更多的供应商锁定风险,并增加了对特定云服务提供商的依赖性。使用您自己部署的本地监控是最灵活和最便宜的,但您必须考虑随之而来的管理和运营开销,因为这需要额外的大型应用程序。

关于监控的问题仍然存在。您可以在谷歌的以下在线书籍中了解更多关于四个黄金信号的信息:landing.google.com/sre/sre-book/chapters/monitoring-distributed-systems/。在以下文章中了解USE(即Utilization Saturation and Errors)方法:www.brendangregg.com/usemethod.html

现在,混合 Windows/Linux Kubernetes 集群进入了视野。重要的是要知道,监控 Windows 机器与监控 Linux 机器有很大不同——您不能使用相同的监控代理;它们必须专门针对特定的操作系统。

即使在 Docker 的情况下,它与操作系统的集成方式与 Linux 和 Windows 不同,这也意味着容器运行时监控必须以不同的方式进行。这就是为什么目前在 Kubernetes 中没有用于监控 Windows 节点的即插即用解决方案的原因。提供最接近的是 Azure Monitor 中的容器监控解决方案(docs.microsoft.com/en-us/azure/azure-monitor/insights/containers),它可以为 Windows 容器提供遥测数据,但尚未与混合 AKS 或 AKS Engine 集成。当然,您仍然可以在 AKS Engine 的机器上手动配置它。

那么,我们还有什么其他解决方案吗?作为更通用的解决方案,我们建议部署一个 Prometheus 实例,它将能够默认监控来自 Linux 工作负载的指标,并可以扩展到监控 Windows 节点和容器。

在您的集群中进行分布式跟踪和聚合日志是复杂的监控主题。在本书中,我们只会涵盖度量监控。如果您对 Kubernetes 的日志记录解决方案感兴趣,请查看官方文档:kubernetes.io/docs/concepts/cluster-administration/logging/。对于分布式跟踪,请考虑阅读关于 Jaeger 的信息:www.jaegertracing.io/

让我们看看如何使用 Prometheus 为混合 Kubernetes 集群提供度量监控。

Prometheus 和监控 Windows 节点

Prometheus (prometheus.io/) 是一个用于度量监控的开源系统,使用 PromQL 语言来探索时间序列数据。它利用了“exporters”和 HTTP 拉取模型的概念,其中 exporters 在指定的 HTTP 端点上公开数据,并定期被 Prometheus 服务器抓取。另外,它还可以使用 HTTP 推送模型,通常不建议使用,但有时会很有用。用于公开度量的格式是一个简单的文本格式,其中每一行代表一个度量的值,大致形式如下:

http_requests_total{method="post",code="200"} 190
http_requests_total{method="post",code="400"} 5

Prometheus 将所有数据存储为时间序列,这些时间序列是同一度量的读数流,覆盖了整个时间范围。exporters 仅公开度量的当前值,而 Prometheus 负责将历史存储为时间序列。在这个例子中,http_requests_total是度量的名称,method是标签名称,"post"是标签值,190是当前的度量值。标签用于为您的时间序列数据提供维度,然后可以在 PromQL 中用于各种操作,如过滤和聚合。单个读数的一般格式是<metric name>{<label name>=<label value>, ...} <metric_value>

您可以在官方文档中阅读更多关于这种格式的信息:github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md

在 Prometheus 之上,您通常会使用 Alertmanager 来配置警报和 Grafana(grafana.com/)或 Kibana(www.elastic.co/products/kibana)来创建仪表板和可视化。以下图表显示了 Prometheus 在高层次上的架构以及它如何监视在 Kubernetes 中运行的 Linux 工作负载:

用于监视 Kubernetes 上 Linux 容器的常见 Prometheus 架构

除了标准的 Prometheus 组件之外,在集群中每个 Linux 节点上运行着两个关键的导出器:cAdvisor,它公开容器运行时指标,以及Node Exporter,它负责公开操作系统和硬件指标。对于 Windows,我们可以使用类似的方案,但我们需要使用不同的导出器,如下图所示:

用于监视 Kubernetes 上 Windows 容器的可能的 Prometheus 架构

在这种情况下,为了公开操作系统和硬件指标,我们使用专门用于 Windows 机器的 WMI Exporter。它还可以公开一些 Docker 指标,但我们还可以打开使用 Docker Engine 本地公开指标的实验性功能,而无需额外的导出器。您可以在文档中阅读更多关于这个 Docker 功能的信息:docs.docker.com/config/thirdparty/prometheus/

一般来说,在 Windows 上,部署导出器作为收集操作系统指标的 Kubernetes DaemonSets 更加困难。正如前几章中提到的,在 Windows 上,您无法运行特权容器,因此无法访问容器运行时信息。这就是为什么在 Kubernetes 中监视 Windows 容器比监视 Linux 容器要困难一些的主要原因——我们必须在 Kubernetes 集群之外直接在主机上配置导出器。现在,让我们看看在本地场景和 AKS Engine 中如何实现这一点。

提供可观察的 Windows 节点

Prometheus 使用的 HTTP 拉模型与可观察性和监视本身之间的关注点分离完全一致。组件或机器负责暴露适当的数据和指标-它允许被观察-而 Prometheus 定期消耗可用数据,这个过程称为抓取。这意味着如果您有一种方法可以在某个 HTTP 端点以 Prometheus 格式暴露指标,您就可以使用 Prometheus 进行监视!它可以是系统服务暴露的硬件遥测,甚至是您在.NET 应用程序中通过额外的 HTTP 端点访问的自己的指标。

现在,有一个问题,如何在 Windows 操作系统上收集指标数据并将其暴露出来。我们对以下内容感兴趣:

  • 与主机机器相关的指标,例如 CPU、内存、网络和 I/O 指标

  • 进程和主机操作系统本身的指标以及性能计数器通常

  • 容器运行时本身的指标

  • 单个容器的指标

  • 在裸机上,此外,关于硬件指标的信息,如 CPU 温度和 ECC 内存校正计数。

对于 Prometheus 来说,在 Windows 上支持出口器的支持仍在扩展,但目前,我们已经可以收集大部分前述的指标。总的来说,WMI Exporter(github.com/martinlindhe/wmi_exporter)是在 Windows 上收集所有与硬件和操作系统相关的指标的推荐出口器。对于 Docker 运行时和容器,我们可以使用 Docker 的一个实验性功能(docs.docker.com/config/thirdparty/prometheus/)来以 Prometheus 格式暴露指标。此外,当在配置中启用容器收集器时,WMI Exporter 还可以暴露一些有用的 Docker 容器指标。

如果您对任何其他 Windows 性能计数器感兴趣,可以使用 Telegraf(www.influxdata.com/time-series-platform/telegraf/)将它们暴露为 Prometheus 格式的指标。我们将在接下来的部分中进行这样的操作,因为在主机上监视 Windows 性能计数器以及容器内部都有非常有效的用例。

安装 WMI Exporter 并在 Docker 中启用 Metrics Server

现在,我们对如何使 Windows 机器对 Prometheus 可观察以及哪些组件可以满足我们的要求有了一些了解。如果您使用 Chocolatey,WMI Exporter 的安装非常简单:

choco install prometheus-wmi-exporter.install

此命令将使用默认配置安装导出器,并在端点http://0.0.0.0:9182上公开指标,如软件包文档中所述:chocolatey.org/packages/prometheus-wmi-exporter.install。对于我们的用例,我们需要启用一些特定的收集器,并且这些信息可以作为参数传递给安装程序。此外,我们应该使安装无人值守,并在机器上安装 Chocolatey(如果缺少)-我们的 PowerShell 脚本将如下所示:

if ((Get-Command "choco" -ErrorAction SilentlyContinue) -eq $null) {
 Invoke-Expression ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1')) | Out-Null
}

choco install prometheus-wmi-exporter.install -y --force --params "`"/EnabledCollectors:cpu,cs,container,dns,logical_disk,logon,memory,net,os,process,service,system,tcp`""

要在 Docker Engine 中启用 Metrics Server,位于http://0.0.0.0:9323,我们可以创建另一个小的 PowerShell 脚本:

Set-Content -Value '{ "metrics-addr" : "0.0.0.0:9323", "experimental" : true }' -Path C:\ProgramData\docker\config\daemon.json
Restart-Service Docker -Force

现在,您必须考虑如何执行安装。对于本地部署,请考虑以下内容:

  • 如果您使用自动化创建 Kubernetes 集群,例如 Ansible,那么您可以添加额外的后配置步骤。

  • 如果您在集群中为您的机器使用裸机映像或 VM 映像,您可以将安装步骤嵌入到映像配置过程中。

  • 如果您使用 Ansible 或 PowerShell Desired State Configuration 来管理您的机器,您也可以使用这些工具触发安装。

在云部署的情况下,一切取决于您是使用托管还是非托管集群:

  • 对于像 AKS 这样的托管部署,您受到服务允许的限制;例如,您可以使用带有自定义脚本扩展的 VMSS。

  • 对于非托管部署,您可以使用与本地部署相同的技术,例如提供预安装服务的自定义 VM 映像,或者使用专门针对您的云服务提供商的解决方案。

对于 AKS Engine,您有三个选项:

我们将演示如何使用专用扩展自定义 AKS Engine 集群部署。

使用 AKS Engine 的扩展

AKS Engine 扩展是一项功能,它允许在部署的后期步骤中进行额外的自定义步骤。例如,您可以通过扩展存储库执行任何提供的 PowerShell 脚本。存储库可以是遵循目录命名约定的任何 HTTP 服务器,这也包括原始的 GitHub 存储库访问端点。要了解有关扩展如何工作的更多信息,请参阅官方文档:github.com/Azure/aks-engine/blob/master/docs/topics/extensions.md。您可以使用winrm扩展作为了解实现细节的良好基础:github.com/Azure/aks-engine/tree/master/extensions/winrm

在集群部署期间可以使用扩展。您不能在运行的集群上启用扩展。此外,由于 SQL Server Helm 图表需要在单个节点上挂载四个卷,我们需要为 Linux 节点使用更大的 VM 类型,例如 Standard_D4_v3,该类型支持最多八个卷。您可以在文档中阅读有关每个 VM 挂载的最大卷数:docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-general

在本书的 GitHub 存储库中,您可以找到一个安装 WMI Exporter 并在 Windows 上启用 Docker Metrics Server 的扩展:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter14/03_aks-engine-windows-extensions/extensions/prometheus-exporters。让我们看看扩展是如何构建的,以及如何使用扩展部署新的 AKS Engine 集群:

  1. PowerShell 脚本v1/installExporters.ps1执行自定义安装逻辑,并具有以下内容:
Param(
    [Parameter()]
    [string]$PackageParameters = "/EnabledCollectors:cpu,cs,container,dns,logical_disk,logon,memory,net,os,process,service,system,tcp"
)

if ((Get-Command "choco" -ErrorAction SilentlyContinue) -eq $null) {
    Invoke-Expression ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1')) | Out-Null
}

choco install prometheus-wmi-exporter.install -y --force --params "`"$PackageParameters`""

Set-Content -Value '{ "metrics-addr" : "0.0.0.0:9323", "experimental" : true }' -Path C:\ProgramData\docker\config\daemon.json
Restart-Service Docker -Force

它将使用 Chocolatey 安装 WMI Exporter,为 Docker 启用 Metrics Server,并在之后重新启动 Docker。

  1. v1/template.json JSON 文件包含一个 ARM 模板,触发 PowerShell 脚本的关键部分如下:
"properties": {
     "publisher": "Microsoft.Compute",
     "type": "CustomScriptExtension",
     "typeHandlerVersion": "1.8",
     "autoUpgradeMinorVersion": true,
     "settings": {
       "fileUris": [
         "[concat(parameters('artifactsLocation'), 'extensions/prometheus-exporters/v1/installExporters.ps1')]"
        ]
     },
     "protectedSettings": {
       "commandToExecute": "[concat('powershell.exe -ExecutionPolicy bypass \"& ./installExporters.ps1 -PackageParameters ', parameters('extensionParameters'), '\"')]"
     }
}

这将为自定义脚本扩展配置属性,该扩展将下载安装脚本,并使用您在集群 apimodel 中传递的参数执行它。

  1. v1/template-link.json是一个通用文件,其中包含要由 AKS Engine 替换的占位符。这样,您的模板将链接到部署。

  2. 现在,创建一个 GitHub 仓库并推送扩展。确保您遵循目录命名约定,例如,存储库中template.json的完整路径应为extensions/prometheus-exporters/v1/template.json。在示例中,我们将使用以下 GitHub 仓库:github.com/ptylenda/aks-engine-windows-extensions

  3. 现在,修改您的 AKS Engine 集群 apimodel,使其为所有 Windows 节点使用扩展(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter14/04_aks-engine-cluster-with-extensions/kubernetes-windows-template.json),并确保您使用vmSize用于 Linux 节点池,该节点池能够挂载超过四个卷:

{
  "apiVersion": "vlabs",
  "properties": 
    ...
    "agentPoolProfiles": [
      {
        "name": "linuxpool1",
        "vmSize": "Standard_D4_v3"
        ...
      },
      {        
        "name": "windowspool2",
        ...
        "extensions": [
            {
                "name": "prometheus-exporters",
                "singleOrAll": "all"
            }
        ]
      }
    ],
    ...
    "extensionProfiles": [
      {
        "name": "prometheus-exporters",
        "version": "v1",
        "rootURL": "https://raw.githubusercontent.com/ptylenda/aks-engine-windows-extensions/master/",
        "extensionParameters": "'/EnabledCollectors:cpu,cs,container,dns,logical_disk,logon,memory,net,os,process,service,system,tcp'"
      }
    ]
  }
}

作为rootURL,您需要提供 GitHub 仓库的原始访问的 HTTP 地址,该地址带有扩展。此外,我们将'/EnabledCollectors:cpu,cs,container,dns,logical_disk,logon,memory,net,os,process,service,system,tcp'作为参数传递给扩展,这些参数将在执行 PowerShell 脚本时使用。

  1. 现在,以与前几章相同的方式部署集群。您也可以使用我们通常的 PowerShell 脚本:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter14/04_aks-engine-cluster-with-extensions/CreateAKSEngineClusterWithWindowsNodes.ps1

  2. 当部署完成后,使用kubectl get nodes -o wide命令来确定其中一个 Windows 节点的私有 IP,例如10.240.0.65

  3. 使用ssh azureuser@<dnsPrefix>.<azureLocation>.cloudapp.azure.com命令 SSH 到主节点,并检查 Windows 节点是否在端口93239182上导出指标:

azureuser@k8s-master-36012248-0:~$ curl http://10.240.0.65:9323/metrics
# HELP builder_builds_failed_total Number of failed image builds
# TYPE builder_builds_failed_total counter
builder_builds_failed_total{reason="build_canceled"} 0
builder_builds_failed_total{reason="build_target_not_reachable_error"} 0
builder_builds_failed_total{reason="command_not_supported_error"} 0
...
azureuser@k8s-master-36012248-0:~$ curl http://10.240.0.65:9182/metrics
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
go_gc_duration_seconds{quantile="0.25"} 0
go_gc_duration_seconds{quantile="0.5"} 0
...

恭喜!现在您的 AKS Engine 集群中的 Windows 节点正在公开可以被 Prometheus 抓取的指标。在下一节中,我们将在我们的集群中安装 Prometheus,并配置它来监视 Linux 和 Windows 节点。

使用 Helm 图表部署 Prometheus

我们的集群基础设施现在是可观察的 - 我们可以部署带有适当配置文件的 Prometheus 并开始监视集群。要部署 Prometheus,我们有几个选项:

在我们的情况下,最好的选择是使用stable/prometheus Helm 图表,因为它需要最少的配置,并且不像通用的 Prometheus Operator 那样复杂。在生产环境中,运行大规模,您应该考虑使用 Prometheus Operator,这样您就可以轻松地为不同的需求部署多个 Prometheus 集群。

安装 Helm 图表

要使用 Helm 图表部署 Prometheus,请执行以下步骤:

  1. 我们将在名为monitoring的单独命名空间中部署我们的监控解决方案。此外,我们需要为 Prometheus 数据持久性定义StorageClass。创建名为prereq.yaml的清单文件,内容如下:
---
kind: Namespace
apiVersion: v1
metadata:
  name: monitoring
  labels:
    name: monitoring
---
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: azure-disk
provisioner: kubernetes.io/azure-disk
parameters:
  storageaccounttype: Standard_LRS
  kind: Managed
  1. 使用kubectl apply -f .\prereq.yaml命令应用清单文件。

  2. 现在,我们需要为stable/prometheus Helm 图表(github.com/prometheus/prometheus)定义值。这个图表是高度可配置的,所以请检查是否需要覆盖任何其他值。创建helm-values_prometheus.yaml文件,并开始编辑它,内容如下(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter14/05_helm_prometheus/helm-values_prometheus.yaml):

server:
  enabled: true
  global:
 scrape_interval: 50s
 scrape_timeout: 15s
 evaluation_interval: 1m
  service:
    type: LoadBalancer
  nodeSelector:
    "kubernetes.io/os": linux
  persistentVolume:
    storageClass: azure-disk

alertmanager:
  enabled: true
  service:
    type: LoadBalancer
  nodeSelector:
    "kubernetes.io/os": linux
  persistentVolume:
    storageClass: azure-disk

nodeExporter:
  enabled: true
  nodeSelector:
    "kubernetes.io/os": linux

pushgateway:
  enabled: true
  nodeSelector:
    "kubernetes.io/os": linux

kubeStateMetrics:
  enabled: true
  nodeSelector:
    "kubernetes.io/os": linux

最重要的部分是确保为所有组件设置适当的nodeSelector,以便 Pod 不会意外地被调度到 Windows 机器上。此外,我们需要提供storageClass的名称,用于处理 PVC。另一个解决方案可能是在集群中将azure-disk设置为默认的storageClass。在 Helm 图表配置中,您还可以影响抓取设置,例如您希望多久执行一次抓取作业。最后,我们使用LoadBalancer服务公开了 Prometheus 和 Alertmanager——当然,这仅适用于开发和测试目的,以便不使用kubectl proxy(这需要对 Grafana 进行额外配置)或使用跳板机。

对于生产场景,请考虑将对 Prometheus 的访问限制在私有网络内,或者在其后面暴露 Ingress,使用 HTTPS,并提供安全的身份验证方法。例如,您可以将 Nginx Ingress 与 Azure Active Directory 集成(kubernetes.github.io/ingress-nginx/examples/auth/oauth-external-auth/)。

在设置较小的scrape_interval值时要小心。太短的抓取间隔可能会导致节点和 Pod 的过载,并导致系统不稳定。您应该始终评估您的导出器在 CPU 使用和 RAM 内存方面的成本。

  1. 继续编辑helm-values_prometheus.yaml文件,并为 Prometheus 提供抓取配置。我们需要确保我们的 WMI Exporter 和 Docker Engine 指标服务器被 Prometheus 服务器抓取。您只能看到 Docker Engine 指标服务器的以下配置;WMI Exporter 的配置几乎相同,除了端口号:
extraScrapeConfigs: |
   - job_name: windows-nodes-docker-metrics-server
     kubernetes_sd_configs:
       - role: node
     scheme: http
     relabel_configs:
     - action: labelmap
       regex: __meta_kubernetes_node_label_(.+)
     - source_labels: [__address__]
       action: replace
       target_label: __address__
       regex: ([^:;]+):(\d+)
       replacement: ${1}:9323
     - source_labels: [kubernetes_io_os]
       action: keep
       regex: windows
     - source_labels: [__meta_kubernetes_node_name]
       regex: (.+)
       target_label: __metrics_path__
       replacement: /metrics
     - source_labels: [__meta_kubernetes_node_name]
       action: replace
       target_label: node
       regex: (.*)
       replacement: ${1}
...

Prometheus 抓取配置可能会变得有点复杂;您可以查看官方文档以获取详细说明:prometheus.io/docs/prometheus/latest/configuration/configuration/。基本配置会抓取带有prometheus.io/scrape: 'true'注释的 API 资源,因此,例如,如果您希望抓取自己的应用 Pod,您需要使用此注释(以及prometheus.io/port)。此外,您可以根据 API 资源直接配置抓取(kubernetes_sd_configs),在这种情况下是node。之后,我们对节点 API 返回的标签执行各种操作:我们确保__address__特殊标签的最终值包含所需的9323端口,并且我们将__metrics_path__定义为/metrics,因此最终,我们将抓取此 HTTP 端点:http://<nodeAddress>:9323/metrics

  1. 使用values文件安装 Prometheus 的 Helm 图表作为prometheus发布:
helm install prometheus stable/prometheus -n monitoring --values .\helm-values_prometheus.yaml --debug
  1. 在安装进行的同时,您可以为stable/grafana Helm 图表定义helm-values_grafana.yaml值文件,我们将使用它来部署 Prometheus 的 Grafana:
nodeSelector:
  "kubernetes.io/os": linux

service:
  type: LoadBalancer

persistence:
  enabled: true
  storageClassName: azure-disk
  size: 20Gi
  accessModes:
   - ReadWriteOnce

adminUser: admin
adminPassword: P@ssword

datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server
      access: proxy
      isDefault: true

同样,我们需要确保 Grafana 仅安排在 Linux 节点上。同样,我们使用负载均衡器公开服务-您应该考虑不同的生产部署策略,或者至少为此公共端点提供适当的身份验证。最后一个重要的事情是确保我们的 Prometheus 实例被添加为 Grafana 中的默认数据源。在这里,您应该使用服务名称通过 DNS 名称进行发现。

  1. 使用以下命令将stable/grafana Helm 图表安装为grafana发布:
helm install grafana stable/grafana -n monitoring --values .\helm-values_grafana.yaml --debug
  1. 现在,等待所有 Pod 准备就绪并且服务接收到外部 IP:
PS C:\src> kubectl get pod,svc -n monitoring
...
NAME                                    TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
service/grafana                         LoadBalancer   10.0.28.94     104.40.19.54   80:30836/TCP   2h
service/prometheus-alertmanager         LoadBalancer   10.0.0.229     40.78.81.58    80:30073/TCP   2h
service/prometheus-server               LoadBalancer   10.0.219.93    40.78.42.14    80:32763/TCP   2h
...

此时,您有三个可以访问的 Web UI:

  • Prometheus 服务器(在我们的示例中,可在http://40.78.42.14访问)

  • Alertmanager(http://40.78.81.58

  • Grafana(http://104.40.19.54

验证部署

验证您是否可以访问服务的外部 IP 并执行一些基本操作:

  1. 打开您的 Prometheus 服务器的 Web UI。

  2. 转到状态并选择目标。

  3. 向下滚动到由作业抓取的windows-nodes-docker-metrics-serverwindows-nodes-wmi-exporter targets。它们应该是绿色的,并且在没有错误的情况下执行——如果不是这种情况,您需要验证您的抓取配置。出于调试目的,您可以直接向集群中的适当 ConfigMap 引入更改。

  1. 在顶部菜单中导航到图形,并在“执行”按钮下方切换到“图形”选项卡。运行一个示例查询,rate(wmi_net_bytes_total[60s]),它将根据最后 60 秒的wmi_net_bytes_total计数器指标绘制每秒接收和发送到 Windows 节点的平均字节数:

  1. 打开 Grafana Web UI,并使用您在 Helm 图表中提供的凭据登录。

  2. 在菜单中点击+,选择仪表板,然后选择添加查询。

  3. 输入一个示例 PromQL 查询,wmi_memory_available_bytes / (1024 * 1024 * 1024),它将以 GB 为单位绘制 Windows 节点上的可用内存:

现在,我们确认我们的监控设置正常工作!您可以在官方文档中深入了解 PromQL:prometheus.io/docs/prometheus/latest/querying/basics/。这是一种广泛而强大的语言,可以实现大部分您的服务水平指标SLIs)来监视您的服务水平目标SLOs)。

在下一节中,我们将探讨如何配置使用 Telegraf 导出任何 Windows 性能计数器。

Windows 性能计数器

Windows 提供了一个名为性能计数器的功能,用于提供有关操作系统、服务、应用程序或驱动程序的性能情况。通常,您使用Windows 管理工具WMI)来获取单个指标值,并使用更高级的应用程序(如 Perfmon)来在本地可视化性能数据。对于.NET Framework 应用程序,您可以直接读取运行时提供的多个计数器;您可以在文档中找到计数器的列表:docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/performance-counters。有了这些指标,您可以轻松监视异常抛出数量的异常波动(甚至无需分析日志)或分析垃圾回收问题。此外,许多经典的.NET Framework 应用程序还公开了自己的性能计数器。

对于 Kubernetes,除了 WMI Exporter 收集的标准性能计数器(尚不支持自定义查询:github.com/martinlindhe/wmi_exporter/issues/87),还有两种情况可以考虑:

  • 收集容器中运行的应用程序的性能计数器

  • 收集来自 Windows 主机的更多性能计数器

这两个问题都可以使用 Telegraf(github.com/influxdata/telegraf)来解决,它是一个通用的、可扩展的代理,用于收集、处理、聚合和编写指标。它支持的输入插件之一是win_perf_countergithub.com/influxdata/telegraf/tree/master/plugins/inputs/win_perf_counters),可以收集和转换 Windows 上可用的任何性能计数器。同时,Telegraf 能够使用prometheus_client输出插件(github.com/influxdata/telegraf/tree/master/plugins/outputs/prometheus_client)以 Prometheus 格式公开收集的指标。完整的解决方案需要准备一个配置文件,将 Telegraf 安装为 Windows 服务,并确保 Prometheus 抓取新的端点。

如果您想要从主机机器收集更多性能计数器,在 AKS Engine 上,您可以使用自定义扩展来实现,就像我们为 WMI Exporter 和 Docker 指标服务器所做的那样。我们将演示第一个场景:如何丰富您的 Docker 镜像,以便在 Kubernetes 上运行的容器公开更多 Prometheus 指标。请注意,您必须始终考虑这是否对您来说是一个有效的用例——在集群中的每个容器中嵌入 Telegraf 会增加 CPU 使用率和 RAM 内存占用。一个一般的经验法则是,您应该仅对可能需要调查复杂性能问题的关键组件使用此方法,或者作为调试目的的临时操作。

使用 Telegraf 服务扩展 Docker 镜像

Windows 上的 Telegraf 安装过程很简单:需要解压文件,提供适当的配置文件,并将 Telegraf 注册为 Windows 服务。要为投票应用程序构建新版本的 Docker 镜像,该镜像在端口9273上公开性能计数器,您可以使用 GitHub 存储库中的源代码(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter14/06_voting-application-telegraf),或者在先前版本的源代码上执行以下步骤:

  1. 在根目录中,创建一个名为telegraf.conf的新文件,其中包含 Telegraf 配置。您可以在此处找到此文件的内容:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter14/06_voting-application-telegraf/telegraf.conf。我们只在以下列出了重要部分:
...
[[outputs.prometheus_client]]
  listen = "0.0.0.0:9273"
  path = "/metrics"
...
[inputs.win_perf_counters]]
  UseWildcardsExpansion = false
  PrintValid = false

  [[inputs.win_perf_counters.object]]
    # Processor usage, alternative to native, reports on a per core.
    ObjectName = "Processor"
    Instances = ["*"]
    Counters = [
      "% Idle Time",
      "% Interrupt Time",
      "% Privileged Time",
      "% User Time",
      "% Processor Time",
      "% DPC Time",
    ]
    Measurement = "win_cpu"
    # Set to true to include _Total instance when querying for all (*).
    IncludeTotal=true
...

我们正在使用prometheus_client输出插件和win_perf_counters输入插件,它配置了多个性能计数器的收集。

  1. 将此文件添加到votingapplication.csproj中,以便将其包含在构建输出中。

  2. 修改Dockerfile.production文件,以便在runtime阶段的开头包含安装 Telegraf 的部分:

...
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2019 AS runtime

WORKDIR /temp
RUN powershell -Command \
    Invoke-WebRequest https://dl.influxdata.com/telegraf/releases/telegraf-1.12.6_windows_amd64.zip -OutFile telegraf.zip \
  ; powershell -Command Expand-Archive -Path telegraf.zip -DestinationPath C:\temp \
  ; Remove-Item -Path telegraf.zip \
  ; mkdir c:\telegraf \
  ; Move-Item -Path c:\temp\telegraf\telegraf.exe -Destination c:\telegraf

WORKDIR /telegraf
RUN powershell -Command \
    mkdir telegraf.d \
  ; .\telegraf.exe --service install --config C:\telegraf\telegraf.conf --config-directory C:\telegraf\telegraf.d
COPY telegraf.conf .
RUN powershell -Command \
    Start-Service telegraf
EXPOSE 9273

...

上述命令下载了 Telegraf 的最新版本,将其安装为 Windows 服务,并提供了之前步骤中的配置。

  1. 使用标签 1.6.0 构建镜像,并像在之前的章节中一样将其推送到 Docker Hub。在我们的情况下,它将是packtpubkubernetesonwindows/voting-application:1.6.0

Telegraf 配置可以通过将自定义 ConfigMap 挂载到容器中的C:\telegraf\telegraf.d目录来在容器运行时进行修改。这是 ConfigMaps 的一个完美用例。

现在,Docker 镜像已准备就绪,可以在投票应用程序的 Helm 图中使用。

部署一个可观察的投票应用程序版本

为了能够抓取容器中 Telegraf 公开的性能计数器,我们需要更新 Helm 图以包括 Docker 镜像的新标签,并更新用于抓取的 Pod 注释。您可以在以下位置找到准备好的 Helm 图:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter14/07_voting-application-telegraf-helm,或者按照以下步骤使用先前的版本:

  1. 在 Helm 图的根目录中打开 PowerShell 窗口。

  2. Chart.yaml文件中,将appVersion增加到与 Docker 镜像标签1.6.0相等。同时,将图表本身的版本增加到0.3.0

  3. templates\service.yaml文件中,为 Service 添加annotations,以便 Prometheus 可以开始在端口9273上抓取服务后面的所有 Pod:

apiVersion: v1
kind: Service
metadata:
  name: {{ include "voting-application.fullname" . }}
  ...
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "9273"
...
  1. 更新templates\deployment.yaml文件,以便投票应用程序前端 Pod 在9273端口上公开 Telegraf 在/metrics端点处导出的数据:
apiVersion: apps/v1
kind: Deployment
...
spec:
  ...
  template:
    ...
    spec:
    ...
      containers:
        - name: {{ .Chart.Name }}-frontend
          ...
          ports:
            ...
            - name: telegraf
 containerPort: 9273
 protocol: TCP
          ...
  1. 确保dev-helm命名空间存在。创建dev-helm.yaml清单文件:
kind: Namespace
apiVersion: v1
metadata:
  name: dev-helm
  labels:
    name: dev-helm
  1. 使用kubectl apply -f .\dev-helm.yaml命令应用清单文件。

  2. Helm 图已准备就绪,可以在投票应用程序的 Helm 图的根目录中执行以下命令:

helm install voting-application . `
 --namespace dev-helm `
 --debug `
 --timeout 900s

或者,如果您已经在集群中安装了此图的先前版本,请使用相同的参数使用helm upgrade命令。

  1. 等待部署完成;您可以使用kubectl get pods -n dev-helm -w命令在另一个 PowerShell 窗口中观察进度。

此时,投票应用程序的新版本已部署到集群中,并且 Prometheus 已经使用kubernetes-service-endpoints抓取作业来抓取 Pod。这在默认配置中已经定义。让我们验证一下是否一切正常:

  1. 在网络浏览器中导航到投票应用程序的外部 IP,并使用网站创建一些流量,持续几分钟。

  2. 在网络浏览器中打开 Prometheus 服务器的外部 IP,在 Graph 面板中打开,并将选项卡切换到 Graph。

  3. Telegraf 配置设置为输出所有带有win_前缀的指标。让我们查询其中一个指标,例如win_aspnet_app_Requests_Failed,这是 ASP.NET 应用程序中失败请求的计数器。使用rate(win_aspnet_app_Requests_Failed{app_kubernetes_io_name="voting-application"}[5m])查询,该查询为每个 Pod 分别提供了过去五分钟内投票应用程序失败请求的平均每秒速率:

现在,您可能想知道为什么我们在某个时间点看到失败请求数量突然增加-您很可能会在您的 Prometheus 中看到相同的情况。答案是在部署 Helm 图表后的几分钟内出现了失败的健康检查(就绪探针)。您可能还记得,SQL Server Helm 图表需要最多 10 分钟才能完全部署。这意味着在这段时间内,投票应用程序 Pod 的就绪探针将以 HTTP 500 状态代码失败。

计算rateirate需要每个时间序列间隔至少两个数据点。这意味着您应该使用间隔值至少比抓取间隔大两倍。否则,您将在图表中看到缺失的数据。

您可以探索我们为每个 Pod 公开的其他性能计数器-Telegraf 的这种配置获得了大量的计数器,例如.NET CLR 中抛出的异常数量,.NET CLR 中的锁定数量(这对于检测重锁定场景可能非常有用!),.NET CLR 垃圾回收统计信息或 IIS 性能计数器。

在下一节中,我们将添加监控谜题的最后一部分:使用prometheus-net NuGet 包直接从.NET Framework 应用程序公开自己的指标。

使用 prometheus-net 监控.NET 应用程序

作为监控基础设施的一部分,您需要直接从应用程序中公开自定义指标,这些指标提供了对业务逻辑的额外仪表和见解。最流行的编程语言都有与 Prometheus 集成的绑定,对于 C#,提供与 Prometheus 集成的库之一是prometheus-netgithub.com/prometheus-net/prometheus-net)。您可以将其用于经典的.NET Framework 和.NET Core,因为它针对的是.NET Standard 2.0。其功能包括以下内容:

  • 导出计数器和仪表

  • 测量操作持续时间,并创建摘要或直方图

  • 跟踪正在进行的操作,并创建具有并发执行代码块数量的仪表

  • 异常计数

此外,对于 ASP.NET Core 应用程序,您可以使用专用的中间件包(www.nuget.org/packages/prometheus-net.AspNetCore)来导出 ASP.NET 指标。不幸的是,对于经典的 ASP.NET MVC,不支持此功能,但可以手动实现类似的功能。

安装 NuGet 包并添加指标

该库提供为 NuGet 包(www.nuget.org/packages/prometheus-net)。要在投票应用程序中启用prometheus-net,请按照以下步骤操作,或者您可以使用可在以下位置找到的源代码的准备版本:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter14/08_voting-application-prometheus-net

  1. 在 Visual Studio 2019 中打开投票应用程序解决方案。

  2. 右键单击 VotingApplication 项目,然后选择管理 NuGet 包....

  3. 找到prometheus-net包并安装它。

  4. 我们需要启动一个 HTTP 监听器来导出指标。在Global.asax.cs文件(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter14/08_voting-application-prometheus-net/Global.asax.cs)中,在Application_Start方法的开头,添加以下行:

var server = new MetricServer(port: 9274);
server.Start();

这将在所有网络接口的/metrics端口9274处公开指标。

  1. 在运行在 IIS 上的应用程序内部使用自定义 HTTP 监听器需要添加网络 ACL 规则以允许 IIS AppPool 用户使用此端口。因此,我们需要扩展Dockerfile.production文件以包括以下命令,例如,在 Telegraf 安装后:
RUN "netsh http add urlacl url=http://+:9274/metrics user=\"IIS AppPool\DefaultAppPool\""
EXPOSE 9274

现在,该应用程序正在公开非常基本的.NET 性能计数器。我们想要添加一些自定义指标,这些指标将特定于我们的投票应用程序。例如,我们将添加两个指标:

  • 计数器:这是自应用程序启动以来已添加到数据库的投票数。然后,我们可以使用计数器来,例如,计算每个时间间隔添加的平均投票数。

  • 直方图:这是用于检索调查结果并对其进行总结的持续时间。

要做到这一点,请按照以下步骤进行:

  1. SurveyController类(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter14/08_voting-application-prometheus-net/Controllers/SurveysController.cs)中,定义两个指标,DbAddedVotesCountGetSurveyResultOperationDuration,作为static readonly字段:
private static readonly Counter DbAddedVotesCount = Metrics.CreateCounter(
    "votingapplication_db_added_votes",
    "Number of votes added to the database.");

private static readonly Histogram GetSurveyResultOperationDuration = Metrics.CreateHistogram(
    "votingapplication_getsurveyresult_duration_seconds",
    "Histogram for duration of GetSurveyResult operation.",
    new HistogramConfiguration { Buckets = Histogram.ExponentialBuckets(0.001, 1.5, 20) });
  1. Vote控制器操作中递增DbAddedVotesCount计数器,在将每个Vote添加到数据库后:
...
    this.voteLogManager.Append(vote);
    this.db.Votes.Add(vote);
    DbAddedVotesCount.Inc();
}
...
  1. 测量获取调查结果的时间以创建直方图。在Results控制器操作中,将对GetSurveyResult的调用包装到using块中,并使用GetSurveyResultOperationDuration来测量时间:
SurveyResult result;
using (GetSurveyResultOperationDuration.NewTimer())
{
    result = this.GetSurveyResult(survey);
}

return this.View(result);
  1. 在进行这些更改后,在指标导出端点,您将看到新的指标:
# HELP votingapplication_db_added_votes Number of votes added to the database.
# TYPE votingapplication_db_added_votes counter
votingapplication_db_added_votes 3
...
# HELP votingapplication_getsurveyresult_duration_seconds Histogram for duration of GetSurveyResult operation.
# TYPE votingapplication_getsurveyresult_duration_seconds histogram
votingapplication_getsurveyresult_duration_seconds_sum 0.5531466
votingapplication_getsurveyresult_duration_seconds_count 7
votingapplication_getsurveyresult_duration_seconds_bucket{le="0.005"} 0
votingapplication_getsurveyresult_duration_seconds_bucket{le="0.01"} 0
...
  1. 构建一个新版本的 Docker 镜像,将其标记为1.7.0,并推送到 Docker Hub。我们将在下一节中使用packtpubkubernetesonwindows/voting-application:1.7.0 Docker 镜像。

如您所见,添加导出自定义指标的功能非常简单和自解释——您无需对现有代码库进行重大更改!

现在,让我们部署应用程序的新版本并测试新的指标。

部署投票应用程序的新版本

我们必须以与上一节相似的方式修改 Helm 图表。必须更新 Docker 镜像并在服务的注释中注册新的抓取端口-由于 Prometheus 不支持在单个抓取作业中使用多个端口(github.com/prometheus/prometheus/issues/3756),我们需要添加第二个作业,该作业将使用新端口。您可以在以下位置找到准备好的 Helm 图表:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter14/09_voting-application-prometheus-net-helm,或者按照以下步骤使用先前的版本:

  1. 在 Helm 图表的根目录中打开 PowerShell 窗口。

  2. Chart.yaml文件中,将appVersion增加到与 Docker 镜像标签1.7.0相等。还要将图表的version增加到0.4.0

  3. templates\service.yaml文件中,为端口9274的服务添加一个新的自定义注释prometheus.io/secondary-port。我们将在新的抓取作业中使用此注释:

apiVersion: v1
kind: Service
metadata:
  name: {{ include "voting-application.fullname" . }}
  ...
  annotations:
    ...
    prometheus.io/secondary-port: "9274"
...
  1. 更新templates\deployment.yaml文件,以便投票应用程序前端 Pod 在应用程序在/metrics端点处公开度量数据的端口9274
apiVersion: apps/v1
kind: Deployment
...
spec:
  ...
  template:
    ...
    spec:
    ...
      containers:
        - name: {{ .Chart.Name }}-frontend
          ...
          ports:
            ...
            - name: app-metrics
 containerPort: 9274
 protocol: TCP
          ...
  1. Helm 图表已准备就绪。可以升级投票应用程序的 Helm 发布-在投票应用程序的 Helm 图表的根目录中执行以下命令:
helm upgrade voting-application . `
 --namespace dev-helm `
 --debug `
 --timeout 900s
  1. 等待部署完成,您可以使用kubectl get pods -n dev-helm -w命令在另一个 PowerShell 窗口中观察进度。

最后一步是添加一个 Prometheus 抓取作业,该作业将处理prometheus.io/secondary-port注释。将来,使用多个端口进行抓取应该更容易,但目前,您必须为此目的添加多个作业:

  1. 在 Prometheus Helm 图表的helm-values_prometheus.yaml文件(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter14/10_helm_prometheus-net/helm-values_prometheus.yaml)中,添加另一个额外的抓取作业。这个作业的定义几乎与默认的kubernetes-service-endpoints完全相同,该默认作业位于github.com/helm/charts/blob/master/stable/prometheus/values.yaml,但有额外的过滤:
   - job_name: kubernetes-service-endpoints-secondary-ports
     kubernetes_sd_configs:
     - role: endpoints
     relabel_configs:
     - action: keep
       regex: true
       source_labels:
       - __meta_kubernetes_service_annotation_prometheus_io_scrape
     - action: keep
 regex: (\d+)
 source_labels:
 - __meta_kubernetes_service_annotation_prometheus_io_secondary_port
     ...
     - action: replace
       regex: ([^:]+)(?::\d+)?;(\d+)
       replacement: $1:$2
       source_labels:
       - __address__
       - __meta_kubernetes_service_annotation_prometheus_io_secondary_port
       target_label: __address__     

以下操作将仅保留具有定义的prometheus.io/secondary-port注释并使用它来定义用于抓取的最终__address__的目标。

  1. 升级 Prometheus 的 Helm 发布:
helm upgrade prometheus stable/prometheus -n monitoring --values .\helm-values_prometheus.yaml --debug
  1. 升级完成后,唯一更新的资源是 ConfigMap,prometheus-server。在 Prometheus 重新加载配置之前,您需要等待一小段时间。

  2. 在 Prometheus web UI 中,导航到状态和目标,并验证新端口的抓取是否正常工作;您应该看到kubernetes-service-endpoints-secondary-ports作业的绿色状态:

  1. 打开投票应用的 Web UI,并在几分钟内添加一些投票。

  2. 在 Prometheus web UI 的 Graph 选项卡中,运行一个示例查询来验证解决方案是否有效。例如,使用sum(votingapplication_db_added_votes)来获取从所有 Pod 添加到数据库的投票总数:

我们的解决方案有效!通过这种方式,您可以导出您在应用程序代码中定义的任何指标,并创建更复杂的查询,用于监视和分析目的。

现在,是时候在 Grafana 中配置仪表板并添加一些警报了。

在 Grafana 中配置仪表板和警报

Prometheus 服务器的 Web UI 非常有限,在大多数情况下仅用于执行基本的即席查询和检查配置。要在 Prometheus 中创建更高级的数据可视化,可以使用 Grafana(grafana.com/),这是一个支持多个数据库的开源分析和监控解决方案。在之前的部分中,我们已经使用 Helm 图表部署了 Grafana 和 Prometheus。

Grafana 提供了多种可视化监控数据的方式,从简单的线图和仪表到复杂的热图。您可以在官方文档中找到有关如何创建可视化的更多信息:grafana.com/docs/grafana/latest/。对于我们的应用程序,我们将演示如何配置一个示例仪表板,其中包括以下可视化:

  • Windows 节点 CPU 使用率的折线图

  • IIS 在过去 5 分钟内处理的平均每秒请求数的仪表

  • 显示在过去 5 分钟内添加到数据库的投票数量的折线图

  • 用于可视化检索调查结果持续时间的直方图的热图

当然,这些图表将不足以完全监视您的应用程序,但我们想展示如何创建仪表板的一般原则。

添加可视化

首先,让我们创建仪表板,并为 Windows 节点的 CPU 使用率添加第一个可视化。请执行以下步骤:

  1. 导航到 Grafana Web UI,并使用 Helm 图表发布中提供的凭据登录。默认用户为admin,密码为P@ssword

  2. 从侧面板中,单击+按钮,然后选择仪表板。

  3. 单击“保存仪表板”按钮,并提供voting application作为名称。

  4. 选择添加查询。

  5. 在第一个指标中提供以下查询:100 - (avg by (instance) (irate(wmi_cpu_time_total{mode="idle"}[2m])) * 100)。此查询使用总 CPU 空闲时间的计数器计算了过去两分钟的平均 CPU 使用率。

  6. 在图例中,提供{{instance}}以使用节点主机名作为标签。

  7. 从左侧面板中选择可视化。对于 Y 轴,在单位中选择 Misc 并选择百分比(0-100)。

  8. 从左侧面板中选择常规。将标题更改为平均 CPU 使用率。您的图表应显示 Windows 节点的 CPU 利用率:

下一步是创建仪表板,显示 IIS 在过去 5 分钟内处理的平均每秒请求数。按照以下步骤进行:

  1. 返回仪表板视图,单击添加面板,然后选择添加查询。

  2. 在第一个指标中提供以下查询:sum((rate(win_aspnet_app_Requests_Total[5m]))) by (app_kubernetes_io_instance)。此查询计算了每个 Pod 的 5 分钟间隔内请求的每秒速率,并通过 Kubernetes 应用程序全局汇总。

  3. 从左侧面板中选择“可视化”。选择仪表板。

  4. 在“显示”区域,选择 Calc 为 Last(非空),在“字段”区域,将单位更改为吞吐量 > 请求/秒(reqps)。

  5. 从左侧面板中选择“常规”。将“标题”更改为“过去 5 分钟内的平均 IIS 请求次数”。您的仪表正在显示当前每秒的平均请求次数:

我们将添加第三个可视化,显示过去五分钟内添加到数据库的投票数的折线图。请按照以下步骤操作:

  1. 返回仪表板视图,点击“添加面板”,选择“添加查询”。

  2. 在第一个指标中提供以下查询:sum(irate(votingapplication_db_added_votes[5m])) by (app_kubernetes_io_instance) * 300。该查询计算了每个 Pod 在 5 分钟间隔内投票数量的增加率,并通过 Kubernetes 应用程序全局汇总。我们需要乘以 300(5 分钟)因为 irate 计算的是每秒的速率。

  3. 将图例格式设置为“过去 5 分钟内的投票数”。

  4. 从左侧面板中选择“常规”。将“标题”更改为“过去 5 分钟内添加到数据库的投票数”。现在您的图应该如下所示:

最后,我们将添加最后一个可视化,即用于可视化检索调查结果持续时间直方图的热图。热图是可视化直方图随时间变化的最有效方式,最近,Grafana 扩展了对 Prometheus 直方图指标的热图的本机支持。执行以下步骤创建可视化:

  1. 返回仪表板视图,点击“添加面板”,选择“添加查询”。

  2. 在第一个指标中提供以下查询:sum(increase(votingapplication_getsurveyresult_duration_seconds_bucket[2m])) by (le)。该查询将转换我们的直方图数据——我们确定了最近两分钟内每个桶的绝对增长率,并用标签 le 汇总每个桶,这是桶的标识符(le小于或等于 的缩写—Prometheus 直方图是累积的)。这样,我们就有了整个应用程序全局的桶,而不是单独的 Pod。

  3. 将图例格式更改为 {{le}},并将格式设置为 热图

  4. 从左侧面板中选择“可视化”。选择“热图”。

  5. 在 Y 轴区域,对于单位,选择时间>秒(s),对于格式,选择时间序列桶。将小数设置为1以显示整洁的数字。将空间设置为0,将舍入设置为2 - 我们的热图具有相对较多的桶,因此它将使显示更加平滑。

  6. 在显示区域,打开显示图例和隐藏零。

  7. 从左侧面板中选择常规。将标题更改为获取调查结果持续时间的热图。检查您的热图,特别是在多个浏览器选项卡中对主网页进行压力测试后!热图通常在暗色主题下看起来更好(您可以在全局的配置菜单中更改):

您可以清楚地看到在每分钟约 300 次请求的压力测试期间,此操作的执行情况。

  1. 最后,返回仪表板视图,保存所有更改,并按您的意愿重新排列可视化:

在下一小节中,我们将展示如何在 Grafana 中配置电子邮件警报。

配置警报

Grafana 除了创建可视化和仪表板外,还能够定义警报规则并向多个渠道发送通知。您可以在官方文档中找到支持的通知渠道列表:grafana.com/docs/grafana/latest/alerting/notifications/。警报与特定的可视化相关联,因此您首先需要为您的用例创建适当的可视化。我们将演示如何在节点上创建高 CPU 使用率的警报。

首先,我们需要配置一个电子邮件通知渠道,请按照以下步骤操作:

  1. Grafana 需要 SMTP 配置来发送电子邮件。获取您的电子邮件提供商的详细信息,并修改 Grafana Helm 图表值文件helm-values_grafana.yaml,以便其中包含节点:
grafana.ini:
  smtp:
    enabled: true
    host: <smtpAddressAndPort>  # For Gmail: smtp.gmail.com:587
    user: <smtpUser>
    password: <smtpPassword>
    skip_verify: true  # Needed for Gmail
    from_address: <emailAddress>
    from_name: <name>

请注意,如果您想使用 Gmail,如果启用了 2FA,则需要生成应用程序密码。

  1. 升级 Grafana 的 Helm 版本:
helm upgrade grafana stable/grafana -n monitoring --values .\helm-values_grafana.yaml --debug
  1. 升级完成后,转到 Grafana Web UI。从左侧面板中打开警报,并选择通知渠道。

  2. 单击新通道。

  3. 填写名称,选择电子邮件类型,并提供电子邮件地址。

  4. 单击“发送测试”以测试您的 SMTP 配置是否正确。如果有任何问题,请检查 Grafana Pod 的日志。几分钟后,您应该会在收件箱中收到测试电子邮件。

当您确认您的通知渠道正常工作时,我们可以继续创建警报本身。我们希望在节点的平均 CPU 使用率超过 80%超过五分钟时收到警报。请按照以下步骤配置此类警报:

  1. 打开我们的仪表板,选择平均 CPU 使用率可视化。从可视化菜单中,选择编辑。

  2. 从左侧面板打开警报,然后单击创建警报。

  3. 按照以下所示配置警报:

  1. 选择您的通知渠道,并可选择自定义通知消息。

  2. 保存仪表板。您会注意到仪表板上有一个心形图标,表示警报状态。

现在,我们需要通过创建一些负载来测试我们的规则。我们可以重用在前几章中创建的StressCpu操作。按照以下步骤执行测试:

  1. 在您的网络浏览器中,导航至http://<applicationExternalIp>/Home/StressCpu?value=100,并重复此操作几次,以确保一些 Pod 开始足够地压力节点。

  2. 检查仪表板。您会注意到健康状况仍然是绿色的,但指标已经处于红色区域:

  1. 等待五分钟,从平均使用率在过去五分钟内超过 80%的时间点开始。您应该通过您的通知渠道收到一封电子邮件:

恭喜!您已成功为 Grafana 中的投票应用程序配置了仪表板,并测试了我们监控系统的警报功能。

摘要

在这一长章中,您学会了如何在 Kubernetes 上运行的 Windows 容器中设置监控。首先,我们看了可用的监控解决方案,并确定了哪些适合我们的 Windows 节点的用例——目前最好的选择是使用专用的 Prometheus 实例与 Grafana 一起。接下来,您学会了如何使用 WMI Exporter 和实验性的 Docker Engine 指标服务使 Windows 节点在硬件、操作系统和容器运行时方面可观察。我们已经展示了如何在 AKS Engine 集群上使用扩展安装和配置这些代理。

接下来的步骤是使用 Helm 图表部署 Prometheus 和 Grafana。您需要确保 Prometheus 抓取作业能够在 Windows 节点上发现新的指标端点。之后,我们专注于监控容器内部和 Windows 性能计数器-我们使用 Telegraf 公开了几个计数器,并配置了 Prometheus 对新端点的抓取。此外,您还学会了如何使用prometheus-net库直接从应用程序代码向 Prometheus 导出自定义指标。最后,作为锦上添花,我们向您展示了如何为投票应用程序在 Grafana 中配置示例仪表板,以及如何为 Windows 节点上的高 CPU 使用率启用电子邮件警报。

下一章将重点介绍灾难恢复和 Kubernetes 备份策略。

问题

  1. 为什么可观测性是监控解决方案中的关键概念?

  2. 您可以使用哪些组件来使用 Prometheus 监视 Windows 节点?

  3. 何时应该使用 Prometheus Operator?

  4. 为什么您需要为 Windows 节点在 Prometheus 中配置额外的抓取作业?

  5. 如何将 Windows 容器中的任何 Windows 性能计数器导出到 Prometheus?

  6. 使用prometheus-net库的好处是什么?

  7. 如何在 Prometheus 中为单个服务配置多个端口进行抓取?

  8. 使用热图可视化 Prometheus 直方图有哪些好处?

您可以在本书的评估中找到这些问题的答案。

进一步阅读

第十五章:灾难恢复

在每个生产系统中,灾难恢复DR)和业务连续性BC)是您必须牢记的关键概念,以确保应用工作负载的可用性。您必须在早期阶段考虑它们,以规划您的集群架构。谚语未能准备,实际上是在准备失败对于操作 Kubernetes 等分布式系统来说再合适不过了。本章将重点介绍运行 Kubernetes 集群时的灾难恢复。本章的范围不包括多区部署和持久卷的异步复制等 BC 最佳实践。

一般来说,灾难恢复包括一套政策、工具和程序,以使关键技术基础设施和系统在自然或人为灾难后能够恢复或继续运行。您可以在 Google 的一篇优秀文章中了解更多关于灾难恢复规划涉及的概念:cloud.google.com/solutions/dr-scenarios-planning-guide。灾难恢复和业务连续性的主要区别在于,灾难恢复侧重于在停机后使基础设施恢复运行,而业务连续性则处理在重大事件期间保持业务场景运行。在 Kubernetes 中,灾难恢复的重要之处在于,您可以基本上专注于对集群的数据和状态进行保护:您需要为有状态的组件制定备份和恢复策略。在 Kubernetes 集群中,最重要的有状态组件是 etcd 集群,它是 Kubernetes API 服务器的存储层。

在本章中,我们将涵盖以下主题:

  • Kubernetes 集群备份策略

  • 备份 etcd 集群

  • 恢复 etcd 集群备份

  • 自动化备份

  • 替换失败的 etcd 集群成员

技术要求

对于本章,您将需要以下内容:

  • 安装了 Windows 10 Pro、企业版或教育版(1903 版或更高版本,64 位)

  • 在您的 Windows 机器上安装 SSH 客户端

  • Azure 帐户

  • 使用 AKS Engine 或本地集群部署的多主 Windows/Linux Kubernetes 集群(适用于某些场景)

要跟着做,您需要自己的 Azure 账户以创建 Kubernetes 集群的 Azure 资源。如果您之前没有为早期章节创建账户,您可以阅读有关如何获取个人使用的有限免费账户的更多信息azure.microsoft.com/en-us/free/

使用 AKS Engine 部署 Kubernetes 集群已在第八章中进行了介绍,部署混合 Azure Kubernetes 服务引擎集群

您可以从官方 GitHub 存储库github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter15下载本章的最新代码示例。

Kubernetes 集群备份策略

Kubernetes 的灾难恢复基本上涉及创建集群状态备份和恢复策略。让我们首先看看 Kubernetes 中有哪些有状态的组件:

  • Etcd 集群(etcd.io/)用于持久化 Kubernetes API 服务器资源的状态。

  • Pod 使用的持久卷。

令人惊讶的是(或者),就是这样!对于主节点组件和运行在工作节点上的 pod,您不涉及任何不可恢复的状态;如果您提供了一个新的替换节点,Kubernetes 可以轻松地将工作负载移动到新节点,提供完整的业务连续性。当您的 etcd 集群被恢复时,Kubernetes 将负责协调集群组件的状态。

让我们看看如何备份和恢复持久卷。这完全取决于您的持久卷是如何提供的。您可以依赖于存储在外部的标准文件系统备份,或者在云支持的 PV 的情况下,您可以使用磁盘快照并将其作为云服务的一部分进行管理。还有一个有趣的快照和恢复功能(目前处于 alpha 状态),用于使用 CSI 插件提供的 PV。这将直接在 Kubernetes 集群级别提供更好的备份和恢复集成。

有一个通用的经验法则,尽量使您的集群工作负载尽可能无状态。考虑使用外部托管服务来存储您的数据(例如,Azure blob 存储,Azure Cosmos DB),这些服务的可用性和数据可靠性由 SLA 保证。

对于 etcd 集群,备份和恢复策略取决于两个因素:您如何存储 etcd 数据以及 Kubernetes 主节点的高可用性拓扑是什么。在 etcd 数据存储的情况下,情况类似于持久卷。如果您使用云卷挂载存储,可以依赖云服务提供商的磁盘快照(这是 AKS Engine 的情况),对于自管理磁盘,可以采用标准的文件系统备份策略。在所有情况下,您还有第三个选择:您可以使用 etcd 本身的快照功能。我们稍后将向您展示如何使用etcdctl命令执行 etcd 的快照和恢复。

关于 Kubernetes 主节点的高可用性拓扑,如第四章中所述,Kubernetes 概念和 Windows 支持,您可以运行堆叠拓扑或外部拓扑用于 etcd。在堆叠拓扑中,etcd 成员作为 Kubernetes pod 在每个主节点上运行。对于外部拓扑,您在 Kubernetes 集群之外运行 etcd 集群。它可能是完全外部的,部署在单独的专用主机上,也可能与主节点共享相同的主机。后者是 AKS Engine 的情况:它运行外部拓扑,但每个主节点都托管一个 etcd 成员作为本机 Linux 服务。对于这两种拓扑,您可以以相同的方式执行备份;唯一的区别在于如何执行恢复。在堆叠拓扑中,通常用于kubeadm部署,您需要在新节点上执行kubeadm init覆盖本地 etcd 存储。对于外部拓扑,您可以简单地使用etcdctl命令。

etcd 集群的外部拓扑具有更多组件,但通常更好地提供业务连续性和灾难恢复。

此外,如果您运行的是 AKS Engine 集群,您可以考虑使用 Azure Cosmos DB(azure.microsoft.com/en-us/services/cosmos-db/)而不是自行管理的 etcd 集群。Cosmos DB 支持暴露 etcd API,并且可以像本地 etcd 集群一样用作 Kubernetes 的后备存储。这样,您可以获得全球分发、高可用性、弹性扩展和 SLA 中定义的数据可靠性。此外,您还可以获得具有地理复制的自动在线备份。您可以在官方文档的 cluster apimodel 中了解更多关于此功能以及如何配置它的信息,网址为github.com/Azure/aks-engine/tree/master/examples/cosmos-etcd

现在,让我们来看看如何备份您的 etcd 集群。

备份 etcd 集群

备份 etcd 集群的过程很简单,但有多种方法可以完成这项任务:

  • 创建 etcd 存储磁盘的备份或快照。这在云场景中尤为重要,您可以轻松地在 Kubernetes 集群之外管理备份。

  • 使用etcdctl命令手动对 etcd 进行快照。您需要自行管理备份文件:将它们上传到外部存储,并应用保留策略。

  • 使用Velero(原名 Heptio Ark (velero.io/)),它可以执行快照,管理外部存储中的快照,并在需要时恢复它们。此外,它还可以使用Restic集成(velero.io/docs/master/restic/)来执行持久卷的备份。

  • 使用etcd-operatorgithub.com/coreos/etcd-operator)在 Kubernetes 之上提供 etcd 集群。您可以轻松管理 etcd 集群并执行备份和恢复操作。如果您计划在环境中管理多个 Kubernetes 集群,可以使用这种方法。

我们将演示第二个选项,即手动快照 etcd——在切换到高级自动化(如 Velero)之前,了解底层发生了什么通常是很重要的。为此任务,您将需要一个多主 Kubernetes 集群;您可以使用 AKS Engine 创建一个。与之前的章节一样,您可以使用 Github 存储库中的准备好的 apimodel 定义[https://github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter15/01_multimaster-aks-engine/kubernetes-windows-template.json],并使用我们通常的 PowerShell 脚本[https://github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter15/01_multimaster-aks-engine/CreateAKSEngineClusterWithWindowsNodes.ps1]部署它。此定义将部署三个主节点以及一个 Linux 工作节点和一个 Windows 节点。

请确保您检查在 Azure 上托管五节点 Kubernetes 集群的预估成本。价格将取决于您部署的区域。

当您的集群准备好后,部署一个应用工作负载,例如,之前章节中的投票应用。然后,按照以下步骤创建 etcd 快照:

  1. 打开 PowerShell 窗口,并使用以下命令 SSH 到其中一个主节点:
ssh azureuser@<dnsPrefix>.<azureLocation>.cloudapp.azure.com
  1. 检查您的 Kubernetes 集群配置。使用kubectl cluster-info dump命令了解更多关于 etcd 设置的信息。您将看到每个主节点都在运行其自己的本地实例(但是外部到集群)的 etcd,并将其作为参数传递给 Kubernetes API 服务器:
azureuser@k8s-master-50659983-0:~$ kubectl cluster-info dump
...
 "--etcd-servers=https://127.0.0.1:2379",
...
  1. 使用etcdctl命令获取 etcd 集群的拓扑结构,该集群在主节点上有成员:
azureuser@k8s-master-50659983-0:~$ sudo etcdctl cluster-health
member b3a6773c0e93604 is healthy: got healthy result from https://10.255.255.5:2379
member 721d9c3882dbe6f7 is healthy: got healthy result from https://10.255.255.7:2379
member 72b3415f69c52b2a is healthy: got healthy result from https://10.255.255.6:2379
cluster is healthy

您可以在 Azure 门户中检查这些是否是主节点的私有 IP 地址。

  1. 按顺序执行以下命令以创建 etcd 的快照:
sudo mkdir -p /backup
ETCDCTL_API=3 sudo -E etcdctl \
 --endpoints=https://127.0.0.1:2379 \
 --cacert=/etc/kubernetes/certs/ca.crt \
 --cert=/etc/kubernetes/certs/etcdclient.crt \
 --key=/etc/kubernetes/certs/etcdclient.key \
 --debug \
 snapshot save \
 /backup/kubernetes-etcd-snapshot_$(date +"%Y%m%d_%H%M%S").db
  1. 备份应该在短时间内完成。您可以使用以下命令检查备份的状态:
azureuser@k8s-master-50659983-0:~$ ETCDCTL_API=3 sudo -E etcdctl --write-out=table snapshot status /backup/kubernetes-etcd-snapshot_20191208_182555.db
+----------+----------+------------+------------+
|   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| b4422ea6 |    28331 |       1034 |     3.2 MB |
+----------+----------+------------+------------+

另外,您应该备份用于访问 etcd 集群的证书和密钥。在我们的情况下,这是不需要的,因为我们将恢复相同的主节点机器。但是在一般的灾难恢复场景中,您将需要它们。

备份准备就绪,让我们看看如何将文件上传到 Azure blob 存储。请注意,不应直接在生产主节点上执行这些操作,特别是在快速安装 Azure CLI 时。我们演示这一点是为了之后创建一个 Kubernetes CronJob,它将运行一个 Docker 容器来执行这些操作。请按照以下步骤操作您的开发集群。

  1. 在本地计算机上打开一个 PowerShell 窗口,并使用az login命令登录到 Azure。

  2. 创建一个服务主体,我们将用它来上传备份到 Azure blob 存储容器:

PS C:\src> az ad sp create-for-rbac `
 --role="Storage Blob Data Contributor" `
 --scopes="/subscriptions/<azureSubscriptionId>/resourceGroups/<aksEngineResourceGroupName>"

Creating a role assignment under the scope of "/subscriptions/cc9a8166-829e-401e-a004-76d1e3733b8e/resourceGroups/aks-engine-windows-resource-group"
...
{
 "appId": "89694083-0110-4821-9510-a74eedf7a27c",
 "displayName": "azure-cli-2019-12-08-19-15-41",
 "name": "http://azure-cli-2019-12-08-19-15-41",
 "password": "67b1f492-caea-463f-ac28-69177f52fecf",
 "tenant": "86be0945-a0f3-44c2-8868-9b6aa96b0b62"
}

复制appIdpasswordtenant以供进一步使用。

  1. 执行以下命令创建一个专用的aksenginebackups存储账户来处理备份。选择与您的 AKS Engine 集群相同的 Azure 位置:
az storage account create `
 --name aksenginebackups `
 --resource-group <aksEngineResourceGroupName> `
 --location <azureLocation> `
 --sku Standard_ZRS `
 --encryption blob
  1. 列出新账户的账户密钥,并复制key1的值以供进一步使用:
az storage account keys list `
 --account-name $aksenginebackups `
 --resource-group <aksEngineResourceGroupName>
  1. 继续使用上一段的 SSH 会话来操作您的开发 AKS Engine 集群主节点。执行以下命令安装 Azure CLI:
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
  1. 使用服务主体的appIdpasswordtenant登录到 Azure:
az login --service-principal \
   -u 1775963c-8414-434d-839c-db5d417c4293 \
   -p 276952a9-fa51-44ef-b6c6-905e322dbaed \
   --tenant 86be0945-a0f3-44c2-8868-9b6aa96b0b62
  1. 为我们的 AKS Engine 集群创建一个新的备份容器。您可以使用任何名称,例如集群的 DNS 前缀:
az storage container create \
 --account-name aksenginebackups \
 --account-key "<storageAccountKey>" \
 --name <dnsPrefix>
  1. 创建一个包含我们在上一段中创建的备份的 blob:
sudo az storage blob upload \
 --account-name aksenginebackups \
 --account-key "<storageAccountKey>" \
 --container-name <dnsPrefix> \
 --name kubernetes-etcd-snapshot_20191208_182555.db \
 --file /backup/kubernetes-etcd-snapshot_20191208_182555.db
  1. 从本地磁盘中删除备份文件:
sudo rm /backup/kubernetes-etcd-snapshot_20191208_182555.db

为了创建服务主体和存储账户,我们在 GitHub 仓库中提供了一个 PowerShell 脚本,网址为github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter15/02_CreateBlobContainerForBackups.ps1

您已成功创建了 etcd 快照并将其上传到 Azure blob 存储。现在,我们将演示如何恢复我们刚刚创建的备份。

恢复 etcd 集群备份

为了演示对现有 AKS Engine 集群进行 etcd 恢复的场景,我们首先需要修改一些 Kubernetes 对象,以后证明备份恢复已经生效。请注意,本节中显示的所有命令都假定您正在运行使用外部 etcd 拓扑的 AKS Engine,etcd 成员在托管 Kubernetes 主控组件的相同机器上运行。对于其他集群,比如本地 kubeadm 设置,目录的结构将会有所不同。

首先,让我们介绍一些对集群状态的更改。例如,如果您的投票应用程序正在运行,请使用以下命令删除相关的Deployment对象:

kubectl delete deployment -n dev-helm voting-application

过一段时间,所有的 pod 都将被终止——假设这是我们的灾难事件,导致集群无法使用。我们将要恢复之前创建并上传到 Azure Blob 存储的名为kubernetes-etcd-snapshot_20191208_182555.db的备份!

如果您已经删除了 SQL Server Deployment 以及 PVCs,那么恢复将不会完全成功。正如我们在前面的章节中提到的,对于 PVs,您需要有一个与 etcd 备份协调的单独的备份策略。然后您可以同时恢复 etcd 快照和相关的 PV 快照。

要执行恢复操作,您需要同时连接到所有三个 Kubernetes 节点。这个操作可以按顺序执行,但是主机上停止和启动 etcd 服务必须同时进行。请按照以下步骤进行:

  1. 打开三个 PowerShell 窗口(尽量让它们同时打开并可见,以便更容易地发出命令)。每个窗口将用于一个单独的 Kubernetes 主控。

  2. 在 Azure 门户中,找到主控节点的私有 IP。您也可以使用 Azure CLI 来完成这个操作。它们应该遵循这样的约定,即主控010.255.255.5,主控110.255.255.6,主控210.255.255.7

  3. 在第一个 PowerShell 窗口中,执行以下命令连接到一个主控节点(在 Azure 负载均衡器后面),并额外使用端口转发,将本地端口5500转发到主控0的 SSH 端口,端口5501转发到主控1的 SSH 端口,端口5502转发到主控2的 SSH 端口:

ssh -L 5500:10.255.255.5:22 `
 -L 5501:10.255.255.6:22 `
 -L 5502:10.255.255.7:22 `
 azureuser@<dnsPrefix>.<azureLocation>.cloudapp.azure.com
  1. 通过这种方式,您可以从本地机器连接到任何您想要的 Kubernetes 主节点。检查您已经连接到哪个主节点,并在剩余的 PowerShell 窗口中创建 SSH 连接到其他两个节点,例如:
# Connection to Master 0 already established

# Master 1
ssh azureuser@localhost -p 5501

# Master 2
ssh azureuser@localhost -p 5502
  1. 现在,您有一组 PowerShell 窗口,可以分别管理每个主节点。第一步是安装 Azure CLI。在所有主节点上执行以下命令:
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
  1. 使用服务主体的appIdpasswordtenant登录到 Azure,就像之前一样。在所有主节点上执行以下命令:
az login --service-principal \
   -u 1775963c-8414-434d-839c-db5d417c4293 \
   -p 276952a9-fa51-44ef-b6c6-905e322dbaed \
   --tenant 86be0945-a0f3-44c2-8868-9b6aa96b0b62
  1. 下载kubernetes-etcd-snapshot_20191208_182555.db快照文件。在所有主节点上执行以下命令:
az storage blob download \
 --account-name aksenginebackups \
 --account-key "<storageAccountKey>" \
 --container-name <dnsPrefix> \
 --name kubernetes-etcd-snapshot_20191208_182555.db \
 --file snapshot.db
  1. 所有 etcd 成员必须从相同的快照文件中恢复。这意味着您必须在所有节点上执行类似的操作,只是使用不同的参数。在每个主节点上,确定 etcd 服务的启动参数(对于 AKS Engine,它作为 systemd 服务运行)。执行以下命令以获取每个主节点的参数:
cat /etc/default/etcd
  1. 您需要捕获每个节点的--name--initial-cluster--initial-cluster-token--initial-advertise-peer-urls。更确切地说,--initial-cluster--initial-cluster-token对于所有主节点都是相同的。我们将使用这些值在每个主节点上初始化一个的 etcd 成员,例如,在我们的集群中,对于主节点0,这些参数如下:
--name k8s-master-50659983-0
--initial-cluster k8s-master-50659983-0=https://10.255.255.5:2380,k8s-master-50659983-1=https://10.255.255.6:2380,k8s-master-50659983-2=https://10.255.255.7:2380
--initial-cluster-token k8s-etcd-cluster
--initial-advertise-peer-urls https://10.255.255.5:2380
  1. 我们可以继续为每个 etcd 集群成员恢复数据。此恢复操作仅创建一个新的数据目录。集群当前正在使用的原始数据目录是/var/lib/etcddisk(它是从云卷挂载的)。我们将把数据恢复到/var/lib/etcdisk-restored,然后交换内容。使用上一步的参数,使用匹配的参数为每个主节点执行此命令:
# Master 0
ETCDCTL_API=3 sudo -E etcdctl snapshot restore snapshot.db \
 --name k8s-master-50659983-0 \
 --initial-cluster k8s-master-50659983-0=https://10.255.255.5:2380,k8s-master-50659983-1=https://10.255.255.6:2380,k8s-master-50659983-2=https://10.255.255.7:2380 \
 --initial-cluster-token k8s-etcd-cluster \
 --initial-advertise-peer-urls https://10.255.255.5:2380 \
 --data-dir=/var/lib/etcddisk-restored \
 --debug

# Master 1
ETCDCTL_API=3 sudo -E etcdctl snapshot restore snapshot.db \
 --name k8s-master-50659983-1 \
 --initial-cluster k8s-master-50659983-0=https://10.255.255.5:2380,k8s-master-50659983-1=https://10.255.255.6:2380,k8s-master-50659983-2=https://10.255.255.7:2380 \
 --initial-cluster-token k8s-etcd-cluster \
 --initial-advertise-peer-urls https://10.255.255.6:2380 \
 --data-dir=/var/lib/etcddisk-restored \
 --debug

# Master 2
ETCDCTL_API=3 sudo -E etcdctl snapshot restore snapshot.db \
 --name k8s-master-50659983-2 \
 --initial-cluster k8s-master-50659983-0=https://10.255.255.5:2380,k8s-master-50659983-1=https://10.255.255.6:2380,k8s-master-50659983-2=https://10.255.255.7:2380 \
 --initial-cluster-token k8s-etcd-cluster \
 --initial-advertise-peer-urls https://10.255.255.7:2380 \
 --data-dir=/var/lib/etcddisk-restored \
 --debug
  1. 快照数据已准备好用于新的 etcd 集群。但首先,我们需要优雅地停止现有的 Kubernetes 主组件;否则,在恢复后,您将处于不一致的状态。

  2. Kubelet 观察/etc/kubernetes/manifests目录,其中存储了主组件的清单文件。对这些清单的任何更改都将由 kubelet 应用于集群;这就是在没有 Kubernetes API 服务器的情况下引导 Kubernetes 主节点的方式。要停止主组件,包括 Kubernetes API 服务器,只需将清单文件移动到另一个目录并在所有主节点上执行以下命令:

sudo mv /etc/kubernetes/manifests /etc/kubernetes/manifests-stopped

几秒钟后,您将看到主组件的 Docker 容器正在停止(使用docker ps命令来查看)。

  1. 现在,在所有主节点上停止 etcd 服务:
sudo service etcd stop
  1. 在所有主节点上停止 kubelet 服务:
sudo service kubelet stop
  1. 准备好恢复的主节点的最后一步是删除在主节点上运行但未使用/etc/kubernetes/manifests目录启动的所有其他 Docker 容器。在所有主节点上执行以下命令:
docker stop $(docker ps -q)
  1. 对 etcd 成员执行实际的数据目录恢复。在所有主节点上执行以下命令。
# Backing up old data directory
sudo mkdir /var/lib/etcddisk-old
sudo mv /var/lib/etcddisk/member /var/lib/etcddisk-old/

# Move the contents of the snapshot directory to the target data directory
sudo mv /var/lib/etcddisk-restored/member /var/lib/etcddisk/
sudo chown etcd -R /var/lib/etcddisk
sudo chgrp etcd -R /var/lib/etcddisk
sudo ls -al /var/lib/etcddisk/member/

# Cleanup
sudo rm -rf /var/lib/etcddisk-restored
  1. 现在我们可以开始使用恢复的快照引导集群。第一步是启动 etcd 集群。在所有主节点上执行以下命令:
sudo service etcd start

您可以使用sudo -E etcdctl cluster-health命令验证 etcd 集群的健康状况。

  1. 将停止的清单文件移回到所有主节点的原始位置。一旦 kubelet 启动,它们将被 kubelet 捡起:
sudo mv /etc/kubernetes/manifests-stopped /etc/kubernetes/manifests
  1. 最后,执行最后一步:在所有主节点上启动 kubelet 服务:
sudo service kubelet start

您可以使用docker ps命令快速验证主组件的容器是否正在启动。

  1. 您可以在新的 PowerShell 窗口中检查集群是否已经努力协调恢复的状态:
PS C:\src> kubectl get pods --all-namespaces
NAMESPACE     NAME                                               READY   STATUS              RESTARTS   AGE
dev-helm      voting-application-8477c76b67-4lkrm                0/1     CrashLoopBackOff    6          9h
dev-helm      voting-application-8477c76b67-7tbmw                0/1     CrashLoopBackOff    6          9h
dev-helm      voting-application-8477c76b67-dls6q                0/1     ContainerCreating   7          9h
dev-helm      voting-application-8477c76b67-dvcqz                0/1     ContainerCreating   7          9h
dev-helm      voting-application-8477c76b67-xttml                0/1     CrashLoopBackOff    9          9h
dev-helm      voting-application-mssql-linux-8548b4dd44-hdrpc    0/1     ContainerCreating   0          9h
kube-system   azure-cni-networkmonitor-6dr8c                     1/1     Running             1          9h
kube-system   azure-cni-networkmonitor-dhgsv                     1/1     Running             0          9h
...

我们的投票应用程序部署正在重新创建。这是一个好消息:快照恢复已经成功。几分钟后,所有的 pod 都将准备就绪,您可以在 Web 浏览器中导航到外部 IP 并再次享受应用程序。

Kubernetes 的Openshift发行版实现了本地 etcd 快照恢复功能。您可以在存储库中的脚本中查看详细信息github.com/openshift/machine-config-operator/blob/master/templates/master/00-master/_base/files/usr-local-bin-etcd-snapshot-restore-sh.yaml。那里的步骤大致类似于我们在本节中所做的。

正如您所看到的,手动恢复场景有点复杂,并且容易出错。在生产场景中,当其他方法失败时,您应该使用此方法;通常最好使用自动化备份控制器,例如 Velero(velero.io/)。

在下一节中,您将学习如何使用 Kubernetes CronJobs 在 AKS Engine 上自动化备份过程。

自动化备份

在本节中,我们将演示如何使用 Kubernetes CronJob 自动化 etcd 集群的备份过程。为此,我们需要一个 Dockerfile,其中安装了etcdctl和 Azure CLI,以便为图像创建快照并将其上传到选定的 Azure blob 容器,就像我们在手动步骤中演示的那样。所有配置和服务主体密码将使用环境变量注入,可以使用 Kubernetes secret 设置。

要为 etcd 快照工作程序创建 Docker 镜像,请按照以下步骤进行:

  1. 使用 Linux 机器或切换到 Windows 的 Docker 桌面中的 Linux 容器。

  2. 打开一个新的 PowerShell 窗口。

  3. 为您的源代码创建一个新目录并导航到该目录。

  4. 创建一个名为Dockerfile的文件,内容如下:

FROM ubuntu:18.04

ARG ETCD_VERSION="v3.3.15"

WORKDIR /temp
RUN apt-get update \
 && apt-get install curl -y \
 && curl -L https://github.com/coreos/etcd/releases/download/$ETCD_VERSION/etcd-$ETCD_VERSION-linux-amd64.tar.gz -o etcd-$ETCD_VERSION-linux-amd64.tar.gz \
 && tar xzvf etcd-$ETCD_VERSION-linux-amd64.tar.gz \
 && rm etcd-$ETCD_VERSION-linux-amd64.tar.gz \
 && cd etcd-$ETCD_VERSION-linux-amd64 \
 && cp etcdctl /usr/local/bin/ \
 && rm -rf etcd-$ETCD_VERSION-linux-amd64

RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash

WORKDIR /backup-worker
COPY ./docker-entrypoint.sh .
RUN chmod +x docker-entrypoint.sh

ENTRYPOINT ["/backup-worker/docker-entrypoint.sh"]

此 Dockerfile 基于 Ubuntu 18.04 Docker 镜像,并从 etcd 的官方发布中安装etcdctl命令。此外,我们安装了 Azure CLI,并将ENTRYPOINT设置为一个自定义 shell 脚本,该脚本在容器启动时执行快照操作。

  1. 现在,创建一个名为docker-entrypoint.sh的文件,内容如下。
#!/bin/bash

snapshot_file="kubernetes-etcd-snapshot_$(date +"%Y%m%d_%H%M%S").db"

ETCDCTL_API=3 etcdctl \
   --endpoints=$SNAPSHOT_ETCD_ENDPOINTS \
   --cacert=/etc/kubernetes/certs/ca.crt \
   --cert=/etc/kubernetes/certs/etcdclient.crt \
   --key=/etc/kubernetes/certs/etcdclient.key \
   --debug \
   snapshot save \
   $snapshot_file

ETCDCTL_API=3 etcdctl --write-out=table snapshot status $snapshot_file

az login --service-principal \
   -u $SNAPSHOT_AZURE_PRINCIPAL_APPID \
   -p $SNAPSHOT_AZURE_PRINCIPAL_PASSWORD \
   --tenant $SNAPSHOT_AZURE_PRINCIPAL_TENANT

az storage container create \
   --account-name $SNAPSHOT_AZURE_ACCOUNT_NAME \
   --account-key "$SNAPSHOT_AZURE_ACCOUNT_KEY" \
   --name $SNAPSHOT_AZURE_CONTAINER_NAME

az storage blob upload \
   --account-name $SNAPSHOT_AZURE_ACCOUNT_NAME \
   --account-key "$SNAPSHOT_AZURE_ACCOUNT_KEY" \
   --container-name $SNAPSHOT_AZURE_CONTAINER_NAME \
   --name $snapshot_file \
   --file $snapshot_file

rm -f $snapshot_file

echo "Backup $snapshot_file uploaded successfully!"

上述脚本自动化了我们在前几节中提供的步骤。这里的想法是,使用环境变量、证书和密钥注入的所有配置和凭据,用于访问 etcd 集群的主机卷必须挂载到指定位置:/etc/kubernetes/certs/。对于 AKS Engine 主节点,此映射将是一对一的。

  1. 使用包含您的 Docker ID 的标签构建图像 - 我们将使用packtpubkubernetesonwindows/aks-engine-etcd-snapshot-azure-blob-job
docker build -t <dockerId>/aks-engine-etcd-snapshot-azure-blob-job .
  1. 使用版本1.0.0标记图像并将图像与所有标记一起推送到 Docker Hub:
docker tag <dockerId>/aks-engine-etcd-snapshot-azure-blob-job:latest <dockerId>/aks-engine-etcd-snapshot-azure-blob-job:1.0.0
docker push <dockerId>/aks-engine-etcd-snapshot-azure-blob-job
  1. 您可以选择在开发环境中直接在 AKS Engine 主节点上运行 Docker 镜像进行测试。SSH 到节点并执行以下命令:
docker run \
 -v /etc/kubernetes/certs:/etc/kubernetes/certs \
 -e SNAPSHOT_ETCD_ENDPOINTS=https://10.255.255.5:2379,https://10.255.255.6:2379,https://10.255.255.7:2379 \
 -e SNAPSHOT_AZURE_PRINCIPAL_APPID=1775963c-8414-434d-839c-db5d417c4293 \
 -e SNAPSHOT_AZURE_PRINCIPAL_PASSWORD=276952a9-fa51-44ef-b6c6-905e322dbaed \
 -e SNAPSHOT_AZURE_PRINCIPAL_TENANT=86be0945-a0f3-44c2-8868-9b6aa96b0b62 \
 -e SNAPSHOT_AZURE_ACCOUNT_NAME=aksenginebackups \
 -e SNAPSHOT_AZURE_ACCOUNT_KEY="<storageAccountKey>" \
 -e SNAPSHOT_AZURE_CONTAINER_NAME=<dnsPrefix> \
 packtpubkubernetesonwindows/aks-engine-etcd-snapshot-azure-blob-job:1.0.0

过一段时间后,作业将结束,并且快照将上传到我们之前创建的容器中。

有了 Docker 镜像准备好后,我们可以创建一个专用的 Kubernetes CronJob来定期运行此操作。请注意,我们为此工作提供了一个最小的设置;您应该考虑在生产环境中使用专用的服务帐户并设置 RBAC。还建议使用 Helm 图表有效地管理此作业。要创建 CronJob,请按照以下步骤进行:

  1. 定义一个本地文件etcd-snapshot-secrets.txt,该文件将用于为您的 CronJob 定义秘密对象:
SNAPSHOT_ETCD_ENDPOINTS=https://10.255.255.5:2379,https://10.255.255.6:2379,https://10.255.255.7:2379
SNAPSHOT_AZURE_PRINCIPAL_APPID=1775963c-8414-434d-839c-db5d417c4293
SNAPSHOT_AZURE_PRINCIPAL_PASSWORD=276952a9-fa51-44ef-b6c6-905e322dbaed
SNAPSHOT_AZURE_PRINCIPAL_TENANT=86be0945-a0f3-44c2-8868-9b6aa96b0b62
SNAPSHOT_AZURE_ACCOUNT_NAME=aksenginebackups
SNAPSHOT_AZURE_ACCOUNT_KEY="<dnsPrefix>"
SNAPSHOT_AZURE_CONTAINER_NAME=<dnsPrefix>
  1. 使用etcd-snapshot-secrets.txt文件创建etcd-snapshot-azure-blob-job-secrets秘密对象:
kubectl create secret generic `
 -n kube-system `
 etcd-snapshot-azure-blob-job-secrets `
 --from-env-file=etcd-snapshot-secrets.txt
  1. 现在,为 CronJob 本身创建etcd-snapshot-cronjob.yaml清单文件:
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: etcd-snapshot-azure-blob-job
  namespace: kube-system
spec:
  schedule: "0 */6 * * *" # (1)
successfulJobsHistoryLimit: 2
  failedJobsHistoryLimit: 2
  jobTemplate:
    spec:
      ttlSecondsAfterFinished: 21600
      activeDeadlineSeconds: 600
      template:
        spec:
          tolerations:
          - key: node-role.kubernetes.io/master  # (2)
 operator: Exists
 effect: NoSchedule
 nodeSelector:
 node-role.kubernetes.io/master: ""
          containers:
          - name: snapshot-worker
            image: packtpubkubernetesonwindows/aks-engine-etcd-snapshot-azure-blob-job:1.0.0  # (3)
            volumeMounts:
            - mountPath: /etc/kubernetes/certs
              name: etcd-certs
            envFrom:
            - secretRef:
                name: etcd-snapshot-azure-blob-job-secrets  # (4)
          volumes:
          - name: etcd-certs
            hostPath:
              path: /etc/kubernetes/certs  # (5)
          restartPolicy: Never
          hostNetwork: true

在这个清单文件中,最重要的部分是定义适当的schedule (1) 的部分。我们使用了0 */6 * * * cron 表达式,这将每 6 小时执行一次快照。为了测试目的,您可以将其设置为* * * * *,以便将作业安排在每分钟执行一次。接下来,我们需要确保 CronJob 的 pod 可以在主节点上调度。我们通过使用taintstolerationsnodeSelector来实现这一点 (2)。这是因为我们需要访问 etcd 证书和密钥,这些必须从主机文件系统中挂载。我们定义 pod 使用我们刚刚创建的packtpubkubernetesonwindows/aks-engine-etcd-snapshot-azure-blob-job:1.0.0镜像 (3)。为了为容器填充环境变量,我们使用我们的秘密对象etcd-snapshot-azure-blob-job-secretssecretRef (4)。最后,我们需要将主机目录/etc/kubernetes/certs挂载到 pod 容器中,以便工作节点可以访问证书和密钥 (5)

  1. 使用kubectl apply -f .\etcd-snapshot-cronjob.yaml命令应用清单文件。

  2. 等待第一次作业执行:

PS C:\src> kubectl get cronjob -n kube-system -w
NAME                           SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
etcd-snapshot-azure-blob-job   0 */6 * * *   False     0        2m              16m
  1. 当作业完成时,您可以检查相关 pod 的日志,并在 Azure Portal (portal.azure.com/)中验证快照是否已上传到您的 Azure blob 容器:

当使用多个 etcd 集群(用于多个 Kubernetes 部署)时,您可以使用etcd-operator (github.com/coreos/etcd-operator) 来实现类似的结果。对于像本演示中的小集群,使用这样一个复杂的解决方案是没有意义的。

恭喜!您已成功设置了一个自动化的 CronJob,用于创建 etcd 集群快照并自动将它们上传到 Azure blob 容器。现在,我们将演示如何替换失败的 etcd 成员,以恢复 etcd 集群的全部操作。

替换失败的 etcd 集群成员

作为一个高可用的数据库,etcd 可以容忍少数故障,这意味着大多数集群成员仍然可用和健康;但是,最好尽快替换失败的成员,以改善整体集群健康状况并最小化多数故障的风险。在生产环境中,始终建议将集群大小保持在两个以上的成员。为了从少数故障中恢复,您需要执行两个步骤:

  1. 从集群中删除失败的成员。

  2. 添加一个新的替换成员。如果有多个失败的成员,依次替换它们。

etcd 文档提供了运行时配置更改的用例列表,您可以在etcd.io/docs/v3.3.12/op-guide/runtime-configuration/中查看。

创建新成员的方式取决于失败的具体情况。如果是主机上的磁盘故障或数据损坏,您可以考虑重用相同的主机,但使用不同磁盘上的数据目录。在主机完全故障的情况下,您可能需要提供一个新的机器并将其用作新的替换成员。我们将演示在 AKS Engine 中重用相同主机并创建具有不同数据目录的成员的情况。这是一个非常特定的用例,但在所有情况下,整体流程都是相同的。

首先,让我们模拟 etcd 集群成员的故障。为此,请按照以下步骤进行:

  1. 使用 SSH 连接到 Kubernetes 主节点之一:
ssh azureuser@<dnsPrefix>.<azureLocation>.cloudapp.azure.com

假设我们连接到具有私有 IP10.255.255.5的主节点0

  1. 验证集群健康状态:
azureuser@k8s-master-50659983-0:~$ sudo etcdctl cluster-health
member b3a6773c0e93604 is healthy: got healthy result from https://10.255.255.5:2379
member 721d9c3882dbe6f7 is healthy: got healthy result from https://10.255.255.7:2379
member 72b3415f69c52b2a is healthy: got healthy result from https://10.255.255.6:2379
cluster is healthy
  1. 使用以下命令在主节点0上停止 etcd 服务。这将模拟集群中成员的故障:
sudo service etcd stop
  1. 再次检查集群健康状况,但这次只提供主节点1和主节点2的端点,它们正常运行:
azureuser@k8s-master-50659983-0:~$ sudo etcdctl --endpoints=https://10.255.255.6:2379,https://10.255.255.7:2379 cluster-health
failed to check the health of member b3a6773c0e93604 on https://10.255.255.5:2379: Get https://10.255.255.5:2379/health: dial tcp 10.255.255.5:2379: connect: connection refused
member b3a6773c0e93604 is unreachable: [https://10.255.255.5:2379] are all unreachable
member 721d9c3882dbe6f7 is healthy: got healthy result from https://10.255.255.7:2379
member 72b3415f69c52b2a is healthy: got healthy result from https://10.255.255.6:2379
cluster is degraded
  1. 记录失败成员的 ID,在我们的案例中是b3a6773c0e93604

现在,让我们演示如何替换失败的成员。请按照以下步骤进行:

  1. 确定失败成员的 ID。我们已经从之前的命令中获得了这些信息,但通常您可以使用sudo etcdctl --endpoints=https://10.255.255.6:2379,https://10.255.255.7:2379 member list命令。

  2. 通过 SSH 登录到具有失败成员的机器。

  3. 使用其 ID 从集群中删除失败的成员:

azureuser@k8s-master-50659983-0:~$ sudo etcdctl --endpoints=https://10.255.255.6:2379,https://10.255.255.7:2379 member remove b3a6773c0e93604
Removed member b3a6773c0e93604 from cluster
  1. 向集群添加一个名为k8s-master-50659983-0-replace-0的新成员;您可以使用任何名称,但通常最好遵循一致的约定。在我们的情况下,该成员将具有与以前相同的 IP 地址:
azureuser@k8s-master-50659983-0:~$ sudo etcdctl --endpoints=https://10.255.255.6:2379,https://10.255.255.7:2379 member add k8s-master-50659983-0-replace-0 https://10.255.255.5:2380
Added member named k8s-master-50659983-0-replace-0 with ID af466a622a247b09 to cluster
  1. 现在,您需要修改 etcd 服务启动参数,以反映此机器上成员的更改。使用文本编辑器(例如vim)以 root 身份打开/etc/default/etcd

  2. --name参数修改为k8s-master-50659983-0-replace-0

  3. --initial-cluster参数修改为k8s-master-50659983-2=https://10.255.255.7:2380,k8s-master-50659983-1=https://10.255.255.6:2380,k8s-master-50659983-0-replace-0=https://10.255.255.5:2380

  4. --initial-cluster-state参数修改为existing

  5. 最后,修改数据目录参数--data-dir为另一个目录,例如/var/lib/etcddisk-replace-0

  6. 保存文件。

  7. 创建数据目录,确保其归etcd所有:

sudo mkdir /var/lib/etcddisk-replace-0
sudo chown etcd /var/lib/etcddisk-replace-0
sudo chgrp etcd /var/lib/etcddisk-replace-0
  1. 启动 etcd 服务:
sudo service etcd start
  1. 过一段时间后,检查集群健康状态:
azureuser@k8s-master-50659983-0:~$ sudo etcdctl --endpoints=https://10.255.255.6:2379,https://10.255.255.7:2379 cluster-health
member 1f5a8b7d5b2a5b68 is healthy: got healthy result from https://10.255.255.5:2379
member 721d9c3882dbe6f7 is healthy: got healthy result from https://10.255.255.7:2379
member 72b3415f69c52b2a is healthy: got healthy result from https://10.255.255.6:2379
cluster is healthy

成功!新成员是健康的,集群的整体状态也是健康的

如果您需要使用具有不同IP 地址的新机器作为 etcd 替换成员,请记得为 Kubernetes API 服务器更改--etcd-servers参数,并且如果您在 etcd 前面使用负载均衡器,请不要忘记更新负载均衡器配置。

恭喜!您已成功替换了 etcd 集群中的一个失败成员。即使新成员托管在同一台虚拟机上,它也具有一个新的 ID(1f5a8b7d5b2a5b68),并且在集群中被视为一个全新的成员。

总结

在本章中,您已经了解了为准备 Kubernetes DR 时应牢记的关键要点。您已经了解了整个 Kubernetes 集群中的有状态组件,以及它们需要使用 etcd 集群和持久卷进行备份和恢复策略的事实。接下来,您将学习如何手动为 Kubernetes etcd 集群执行快照,并将其上传到 Azure blob 容器。然后,我们使用此快照将 Kubernetes 集群恢复到先前的状态,并验证了恢复成功。除此之外,您利用了所有新知识,以创建一个用于创建 etcd 快照(用于 AKS Engine)并将其上传到 Azure blob 容器的 Docker 镜像的快照工作者。我们使用此 Docker 镜像创建了一个 Kubernetes CronJob 来执行备份,每 6 小时执行一次。我们最后讨论的主题是如何替换 AKS Engine 中失败的 etcd 成员。有了这些知识,您应该能够为 Kubernetes 集群创建可靠的灾难恢复计划。

本书的最后一章将重点讨论运行 Kubernetes 的生产考虑。您可以将本章视为一组松散耦合的建议和不同生产场景的最佳实践。

问题

  1. 灾难恢复(DC)和业务连续性(BC)之间有什么区别,它们之间的关系是什么?

  2. 在 Kubernetes 中需要备份哪些组件,以确保能够恢复集群状态的可能性?

  3. 什么是 etcd 快照?

  4. Velero 和 etcd-operators 是什么,它们的用例是什么?

  5. 恢复 etcd 快照的高级步骤是什么?

  6. Kubernetes CronJob 是什么,您如何使用它来自动化 etcd 集群的备份策略?

  7. 替换失败的 etcd 集群成员的高级步骤是什么?

您可以在本书的评估中找到这些问题的答案。

进一步阅读

第十六章:运行 Kubernetes 的生产考虑

您已经到达本书的最后一章了——干得好!在这一简短的章节中,我们将为您提供运行 Kubernetes 在生产环境中的各种最佳实践和建议。对于每种软件工程方法或工具,总是有两个世界——您如何在开发中使用它以及您如何在生产中使用它。对于 Kubernetes 来说,在生产中运行需要更多的运维开销,因为您希望以高可用性和可靠性运行您的工作负载,通常规模较大。您必须考虑如何对集群本身进行升级,以及如何对底层操作系统进行补丁,确保业务的连续性。如果您在企业数据中心的隔离网络中运行 Kubernetes,您可能需要在 Docker 和 Kubernetes 的所有组件中进行网络代理配置。

此外,确保使用“基础设施即代码”和“不可变基础设施”方法可再生地配置您的集群非常重要。但这还不是全部——您肯定希望以声明方式管理您的集群工作负载(类似于您的基础设施),为此,您可以采用 GitOps 方法。我们在本章描述的所有概念也可以应用于仅 Linux 集群和混合 Windows/Linux 集群。

本书的最后一章将涵盖以下主题:

  • 可再生地配置集群

  • Kubeadm 的限制

  • 升级集群

  • 操作系统补丁

  • 为 Docker 守护程序和 Kubernetes 配置网络代理

技术要求

对于本章,您将需要以下内容:

  • 已安装 Windows 10 专业版、企业版或教育版(1903 版或更高版本,64 位)

  • Azure 帐户

  • 已安装 Helm

  • 使用 AKS Engine 或本地集群部署的 Windows/Linux Kubernetes 集群

要跟着做,您需要自己的 Azure 帐户来为 Kubernetes 集群创建 Azure 资源。如果您还没有为之前的章节创建帐户,您可以在这里阅读更多关于如何获得个人使用的有限免费帐户:azure.microsoft.com/en-us/free/

使用 AKS Engine 部署 Kubernetes 集群已在第八章中介绍过,部署混合 Azure Kubernetes 服务引擎集群

您可以从官方 GitHub 存储库下载本章的最新代码示例:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter16

可重现地配置集群

首先,让我们看看您如何处理配置您的集群和基础设施以及如何在持续集成持续部署CI/CD)管道中作为一部分声明性地管理您的应用程序工作负载。在所有情况下,设置任何基础设施即代码方法都比仅使用基础设施更加困难和复杂,但最终会有很大的回报。您可以获得配置一致性,在引入复杂更改时的简单性,可测试/可分析的基础设施更改以及开发工作流程的任何阶段的可重现环境。

用于集群的基础设施即代码

基础设施即代码IaC)简而言之,是仅使用声明性配置文件管理整个 IT 基础设施的概念。这意味着您的基础设施状态被捕获在配置文件中,并且使用专用工具应用环境更改,而不是使用脚本或交互式工具进行物理硬件配置。对于 Azure,您可以使用Azure 资源管理器ARM)模板(docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authoring-templates)来描述您的基础设施,或者使用通用的 IaC 工具,如 Terraform(www.terraform.io/)。实际上,在部署 AKS Engine 集群时,您已间接使用了 ARM 模板,您可以将 AKS Engine 工具视为创建复杂 ARM 模板的另一层抽象。

您甚至可以进一步采用 IaC 方法:不可变基础设施IM)。在 IM 的情况下,部署后从不修改任何机器上的任何配置。如果需要进行修复,您必须使用修复的基础镜像构建新的机器,并取消配置旧的机器。这听起来可能很极端,但在虚拟机世界和裸机环境中都可以轻松实现。

Packer (www.packer.io/) 是帮助您引入这种虚拟和裸机机器范式的最佳工具之一。但是如果您仔细考虑一下,我们在本书中已经大量使用了 IaC 和 IM,但是在不同的、更高的级别上。

Docker 本身就是不可变基础设施的体现,您可以将软件作为不可变的操作系统容器映像进行传送,就像它们是 VM 映像一样。Kubernetes 可以被视为管理您的应用程序工作负载的不可变容器基础设施的平台——每当您创建一个新的 Docker 映像并部署新版本的部署时,您只是在创建新的容器并丢弃旧的容器。如果您使用声明性方法来管理您的 Kubernetes 对象(至少使用kubectl apply -f),您最终会得到整洁的基础设施即代码

这篇长篇介绍向我们展示了一些可以被视为为 Kubernetes 提供基础设施和部署集群的建议,从最低到最高级别:

  • 始终使用基础设施即代码不可变基础设施方法为集群提供底层基础设施,使用适合工作的正确工具。Terraform 或 ARM 模板在这两种情况下都非常适合这项任务。AKS Engine (github.com/Azure/aks-engine) 是建立在 ARM 模板之上的不可变基础设施工具的完美示例。如果您想为集群节点部署新版本的 VM 映像,您需要创建一个新的节点池并使用新映像,然后停用旧的节点池。避免使用最初并非用于此目的的工具,比如 Ansible。

  • 在您的基础设施上创建 Kubernetes 集群本身时,请使用“基础设施即代码”概念。诸如 Ansible(www.ansible.com/)、Powershell Desired State Configuration(docs.microsoft.com/en-us/powershell/scripting/dsc/overview/overview?view=powershell-6)或专用的 kubespray(github.com/kubernetes-sigs/kubespray)等工具非常适合这项任务。AKS Engine 将基础设施的提供和集群部署完美地结合在一个工具中。如果您需要托管的 Kubernetes 服务,那么再次使用 Terraform 或 ARM 模板。但是不要将它们用于自管理集群来提供软件,即使它们有能力这样做,它们最初也不是为此而设计的。

  • 使用 Docker 和 Kubernetes 集群作为应用工作负载的“不可变基础设施”平台。使用专用的声明性工具来管理这个平台,比如 Kustomize(kustomize.io/)或 Helm(helm.sh/)。将 Helm chart 管理提升到一个更高的、也是声明性的水平——使用 Helmfile(github.com/roboll/helmfile)或 Flux(github.com/fluxcd/flux)。您将不必再担心运行helm upgrade命令!但是再次强调,不要使用那些本来不是为此而设计的工具,比如 Ansible 或 Terraform,即使它们有被宣传为能够管理 Kubernetes 对象或 Helm Charts 的模块或提供者。您会冒着绑定到一个不具备所有 Kubernetes 功能并很快过时的自定义 API 的风险。

如果您想以声明性方式管理多个 Kubernetes 集群,一定要密切关注目前处于 alpha 状态的 Kubernetes Cluster API(cluster-api.sigs.k8s.io/introduction.html)。这个 Kubernetes 项目将允许您创建一个特殊的 Kubernetes 管理集群,在那里您可以操作集群或机器作为 Kubernetes 自定义资源。

总之,始终使用正确的工具来完成工作!这就是为什么我们现在将看一看 Flux 和 GitOps 方法。

应用工作负载的 GitOps

Flux (github.com/fluxcd/flux) 是一个工具,它可以自动确保 Kubernetes 集群的状态与 Git 中的配置(清单和 Helm 图表)匹配。这种方法符合 GitOps 的理念,GitOps 是一种由 Weaveworks 提出的管理 Kubernetes 集群和应用程序的方式,其中 Git 仓库是声明性基础设施和应用工作负载的唯一真相来源。这种方法完全符合“基础设施即代码”的范式。此外,你可以很好地分离关注点:开发人员对集群状态或应用程序配置进行更改,这些更改存储在 Git 仓库中(通过完整的 CI 流水线),专用的 GitOps 组件负责将配置应用到 Kubernetes 集群。你可以得到清晰的边界,并且始终可以确保仓库中的内容反映了实际的集群状态。

让我们看看如何使用 Flux 来管理运行我们在本书中实施的投票应用程序的集群。为此,您需要一个 AKS Engine 集群,该集群具有能够处理每个节点超过 4 个卷挂载的 Linux 节点 - 您可以使用以下集群 ApiModel:github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter16/01_aks-engine-flux/kubernetes-windows-template.json

Flux 中对 Helm 3 的支持目前处于开发状态。您可以在这里跟踪进展:github.com/fluxcd/helm-operator/issues/8。因此,出于这个原因,我们需要为 Flux 组件使用自定义镜像,但在您阅读本文时,支持可能已经处于稳定状态。

首先,让我们创建我们的仓库,作为 Kubernetes 集群的真相来源。请按照以下步骤操作:

  1. 创建一个新的 GitHub 仓库。我们将使用 github.com/hands-on-kubernetes-on-windows/voting-application-flux 进行演示。

  2. charts/voting-application目录中,放置投票应用 Helm 图表。您可以在这里找到最新的版本(在此版本的 Flux 中,post-install钩子和等待功能不正确工作,需要进行小的变通):github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/tree/master/Chapter16/02_voting-application-flux/charts/voting-application

  3. namespaces目录中,创建带有命名空间定义的demo.yaml文件:

apiVersion: v1
kind: Namespace
metadata:
  labels:
    name: demo
  name: demo
  1. storageclasses目录中,创建带有StorageClass定义的azure-disk.yaml文件:
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: azure-disk
provisioner: kubernetes.io/azure-disk
parameters:
  storageaccounttype: Standard_LRS
  kind: Managed
  1. releases目录中,创建voting-application.yaml文件,其中包含我们投票应用的HelmRelease自定义资源。这个自定义资源由 Flux Helm Operator 处理:
apiVersion: helm.fluxcd.io/v1 
kind: HelmRelease
metadata:
  name: voting-application
  namespace: demo
  annotations:
    fluxcd.io/automated: "true"
spec:
  releaseName: voting-application
  helmVersion: v3
  timeout: 1200
  wait: false
  rollback:
    enable: false
  chart:
    git: ssh://git@github.com/hands-on-kubernetes-on-windows/voting-application-flux
    ref: master
    path: charts/voting-application
  1. 将更改推送到您的 GitHub 存储库。

Flux 不遵循任何目录约定——如何定义结构取决于您。它所做的就是在存储库中搜索 YAML 文件。

我们已经定义了我们的存储库的真实来源。现在,让我们将 Flux 部署到我们的集群中,它能够处理 Helm 3 图表。执行以下步骤(或者您可以使用 PowerShell 脚本:(github.com/PacktPublishing/Hands-On-Kubernetes-on-Windows/blob/master/Chapter16/03_DeployFlux.ps1

  1. 以管理员身份打开一个新的 PowerShell 窗口。

  2. 使用 Chocolatey 安装fluxctl

choco install fluxctl
  1. 为 Flux 组件创建一个专用的fluxcd命名空间:
kubectl create namespace fluxcd
  1. 添加 Flux Helm 存储库:
helm repo add fluxcd https://charts.fluxcd.io
  1. 安装 Flux Helm 图表。您需要确保所有组件的nodeSelector设置为在 Linux 节点上运行。将git.url值设置为您的 GitHub 存储库:
helm upgrade -i flux fluxcd/flux `
 --namespace fluxcd `
 --set "nodeSelector.`"kubernetes\.io/os`"=linux" `
 --set "memcached.nodeSelector.`"kubernetes\.io/os`"=linux" `
 --set "helmOperator.nodeSelector.`"kubernetes\.io/os`"=linux" `
 --set git.url=git@github.com:hands-on-kubernetes-on-windows/voting-application-flux `
 --debug
  1. 应用 HelmRelease 自定义资源定义的官方清单(这里我们使用来自helm-v3-dev分支的开发清单):
kubectl apply -f https://raw.githubusercontent.com/fluxcd/helm-operator/helm-v3-dev/deploy/flux-helm-release-crd.yaml
  1. 安装 Flux Helm 操作员的 Helm 图表。这是来自开发分支的操作员版本,支持 Helm 3。记得确保 Linux 的nodeSelector
helm upgrade -i helm-operator fluxcd/helm-operator `
 --namespace fluxcd `
 --set git.ssh.secretName=flux-git-deploy `
 --set configureRepositories.enable=true `
 --set configureRepositories.repositories[0].name=stable `
 --set configureRepositories.repositories[0].url=https://kubernetes-charts.storage.googleapis.com `
 --set extraEnvs[0].name=HELM_VERSION `
 --set extraEnvs[0].value=v3 `
 --set image.repository=docker.io/fluxcd/helm-operator-prerelease `
 --set image.tag=helm-v3-dev-ca9c8ba0 `
 --set "nodeSelector.`"kubernetes\.io/os`"=linux" 
  1. 使用fluxctl检索必须添加为部署密钥的公共 SSH 密钥:
fluxctl identity --k8s-fwd-ns fluxcd
  1. 复制密钥并在 Web 浏览器中打开您的 GitHub 存储库。

  2. 转到设置和部署密钥。

  3. 添加具有写访问权限的密钥。

  4. 现在,您可以等待一小段时间,直到 Flux 自动同步存储库,或使用此命令强制同步:

fluxctl sync --k8s-fwd-ns fluxcd
  1. 观察使用kubectl get all -n demo命令创建组件。您还可以使用kubectl logs命令跟踪 Helm 操作员日志,特别是在安装 Helm 发布过程中出现任何问题时:
PS C:\src> kubectl get all -n demo
NAME                                                  READY   STATUS    RESTARTS   AGE
pod/voting-application-5cb4987765-7ht4x               0/1     Running   1          2m
pod/voting-application-5cb4987765-dstml               0/1     Running   1          2m
...

在前面的步骤中,我们使用了命令式命令,就像 Flux 的官方指南中一样。当然,您也可以使用声明性清单和带有 Helm 发布值的 YAML 文件。

正如您所看到的,整个过程是完全自动的。您在 Git 存储库中定义状态,Flux 会自动处理将更改应用于集群。现在,让我们测试一下在集群状态中推出更改的工作原理。例如,我们将更改我们在 Voting Application 中使用的图像标记,就好像我们正在推出应用程序的新版本一样:

  1. 在您的集群状态存储库中,开始编辑charts/voting-application/Chart.yaml

  2. version更改为0.4.1以指示图表版本本身已更改。

  3. appVersion更改为不同的 Voting Application 图像标记。例如,我们可以使用1.5.0中的一个先前版本。

  4. 保存更改,提交到存储库,并推送到 GitHub。

  5. 等待更改自动同步或使用fluxctl sync --k8s-fwd-ns fluxcd命令强制同步。

  6. 执行kubectl get pods -n demo命令以查看资源是否正在重新创建:

PS C:\src> kubectl get pods -n demo
NAME                                              READY   STATUS              RESTARTS   AGE
voting-application-55fb99587d-rjvmq               0/1     Running             0          16s
voting-application-55fb99587d-whrwv               1/1     Running             0          79s
voting-application-55fb99587d-x9j8q               0/1     ContainerCreating   0          79s
voting-application-5cb4987765-g2lx8               1/1     Terminating         0          21m
  1. 描述其中一个新的 pod 以验证它是否使用了所需的 Docker 图像标记创建:
PS C:\src> kubectl describe pod -n demo voting-application-55fb99587d-rjvmq
...
Containers:
 voting-application-frontend:
 Container ID: docker://61e207885bcfc3bde670702e342345127dcf0d6e782609bc68127078fc007034
 Image: packtpubkubernetesonwindows/voting-application:1.6.0

恭喜!您已成功使用 Flux 设置了 GitOps 管道。在生产环境中,您可以通过向您的 Git 存储库添加与 CI/CD 组件集成来轻松扩展管道,例如,在将每个拉取请求合并到集群状态存储库之前进行验证。您可以在以下文章中了解更复杂的管道:www.weave.works/blog/what-is-gitops-really

在下一节中,我们将看一下 kubeadm 在生产用例中的限制。

Kubeadm 限制

Kubeadm(github.com/kubernetes/kubeadm)是一个命令行工具,用于提供 Kubernetes 集群,专注于以用户友好的方式执行必要的操作,使得最小可行的安全集群能够运行起来——我们在第四章《Kubernetes 概念和 Windows 支持》中介绍了这个工具,并在第七章《部署混合本地 Kubernetes 集群》中使用了它。这个工具仅限于特定的机器和 Kubernetes API 通信,因此,它通常被设计为其他管理整个集群的自动化工具的构建块。你会发现,诸如 kubespray 之类的其他复杂自动化工具是建立在 kubeadm 之上的。

从 Kubernetes 1.13 开始,kubeadm 被认为是稳定的,并且可以用于生产环境。但即使当前的核心功能集是稳定的,你也应该考虑一些限制,这些限制可能使 kubeadm 不适合你的生产 Kubernetes 部署:

  • kubeadm 对 Windows 节点仅有初始支持,并且关于此支持的 API 可能会发生变化。这使得混合集群的生产部署变得困难——目前唯一的选择是在 Windows 节点上手动配置 Kubernetes 组件,并将它们加入到现有的 Linux 集群中。当然,如果你在 Azure 上运行,你可以使用 AKS 或 AKS Engine 在生产环境中运行 Windows 容器工作负载。

  • 使用 kubeadm 现在可以实现高可用的 Kubernetes 集群设置(具有堆叠和内部 etcd 拓扑),但仍然相对复杂。你可以在官方文档中阅读更多信息:kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/。此外,目前没有简单的方法使用 kubeadm 之后管理 etcd 集群,这意味着诸如 kubespray 之类的解决方案提供了更多的灵活性。但当然,这是以目前不支持 Windows 为代价的。

  • kubeadm 不能用于加入新节点到已经在没有 kubeadm 的情况下引导的现有集群中。

总的来说,对于混合 Windows/Linux Kubernetes 集群,如果不能使用 AKS 或 AKS Engine,那么没有完美的方式来提供生产就绪的集群。使用 kubeadm 仍然是设置这种集群的唯一半自动化方式。

现在,让我们看看如何将您的 Kubernetes 集群升级到更新的版本。

升级集群

在生产环境中运行 Kubernetes 集群肯定需要在某个时候将 Kubernetes 组件升级到更新的版本。您执行升级的方式取决于您用于引导和管理集群的工具。但总的来说,高层次的过程如下:

  1. 升级主节点上运行的组件。

  2. 升级额外的主节点上运行的组件。

  3. 升级工作节点。

有一个重要的规则,您必须遵循以确保安全升级:您只能一次升级集群一个次要版本。这意味着,例如,版本为 1.16 的集群只能升级到 1.17,您不能直接跳到 1.18。这是因为 Kubernetes 主组件的版本差异策略,最多只允许运行一个次要版本的差异。Kubernetes 次要版本发布的预期节奏是三个月一次,这意味着您可能需要经常运行升级过程,特别是考虑到每个次要版本将维护大约九个月。您可以在官方文档中阅读所有组件的政策:kubernetes.io/docs/setup/release/version-skew-policy/

根据集群的引导方式,确切的升级步骤会有所不同。例如,对于 kubeadm 集群,升级将在同一台机器上进行。但是,如果您使用的是 AKS 或 AKS Engine,该过程将符合不可变基础设施范式:主节点和工作节点将依次替换为运行较新版本 Kubernetes 组件的虚拟机。更详细地说,对于主节点,自动升级过程如下:

  1. 将节点标记为不可调度,并排空现有的 Pod。

  2. 删除物理虚拟机。现在,控制平面的大小为N-1个节点。

  3. 创建一个新的虚拟机,安装新版本的 Kubernetes 组件。

  4. 将新的虚拟机添加到集群,并应用任何现有的标签、注释或节点污点。现在,数据平面的大小再次为N

对于工作节点,该过程类似,并包括以下步骤:

  1. 创建一个新的虚拟机,安装新版本的 Kubernetes 组件。

  2. 将新的 VM 添加到集群中。现在,数据平面的大小为 M+1

  3. 如果已经有任何 pod 被调度到新节点上,请将它们驱逐出去。

  4. 将任何现有的标签、注释或污点应用到新节点上。

  5. Cordon 旧节点并排空现有的 pod。

  6. 删除旧的 VM。现在,数据平面的大小再次为 M

将工作节点升级为添加额外的节点(而不是首先删除现有节点)的原因是为了确保数据平面工作负载的集群容量不会缩小。这确保了升级对用户来说是完全透明的。您可以在以下链接中阅读有关 AKS 升级程序的更多信息:docs.microsoft.com/en-us/azure/aks/upgrade-cluster,以及有关 AKS Engine 的信息:github.com/Azure/aks-engine/blob/master/docs/topics/upgrade.md

您可以使用 AKS 和 AKS Engine 中使用的 不可变基础设施 方法进行升级,以执行使用不同工具引导的集群的手动升级,只要工具集允许添加新的主节点和工作节点。

现在让我们执行一个使用 AKS Engine 创建的 Kubernetes 集群(带有 Windows 节点)的升级。在本演示中,我们运行的是一个版本为 1.16.1 的集群,这是我们在之前章节中创建的。您将需要集群 ApiModel,这是您用于初始部署的。要执行升级,请按照以下步骤进行:

  1. 打开 PowerShell 窗口。确定可用的 Kubernetes 版本,用于升级带有 Windows 节点的 AKS Engine 集群。运行以下命令:
PS C:\src> aks-engine get-versions --version 1.16.1 --windows
Version Upgrades
1.16.1 1.17.0-alpha.1, 1.17.0-alpha.2, 1.17.0-alpha.3, 1.17.0-beta.1
  1. 让我们将集群升级到最新版本 1.17.0-beta.1。如果您没有 AKS Engine 服务主体,您必须生成一个新的,因为不可能检索现有主体的密码。要执行此操作,请使用以下命令:
az ad sp create-for-rbac `
 --role="Contributor" `
 --scopes="/subscriptions/<azureSubscriptionId>/resourceGroups/<resourceGroupName>"

注意 appIdpassword,在升级命令中会用到它们。

  1. 执行以下命令进行升级。您必须指定生成的集群 ApiModel:
aks-engine upgrade `
 --subscription-id <azureSubscriptionId> `
 --api-model .\_output\<dnsPrefix>\apimodel.json `
 --location <azureLocation> `
 --resource-group <resourceGroupName> `
 --upgrade-version "1.17.0-beta.1" `
 --auth-method client_secret `
 --client-id <appId> `
 --client-secret <password>
  1. 升级可能需要大约 50 分钟(每个节点 10 分钟),具体取决于您的集群大小。如果您的集群中使用单节点控制平面,则在升级期间将无法访问 Kubernetes API 一段时间。升级完成后,运行以下命令验证节点是否运行所需版本的 Kubernetes。
PS C:\src> kubectl get nodes
NAME                        STATUS   ROLES    AGE     VERSION
1754k8s010                  Ready    agent    17m     v1.17.0-beta.1
1754k8s012                  Ready    agent    26m     v1.17.0-beta.1
k8s-linuxpool1-17543130-0   Ready    agent    3m44s   v1.17.0-beta.1
k8s-linuxpool1-17543130-2   Ready    agent    9m51s   v1.17.0-beta.1
k8s-master-17543130-0       Ready    master   48m     v1.17.0-beta.1

在生产集群中,特别是如果您正在运行带有扩展或专用 VM 镜像的定制集群,则建议在使用完全相同规范创建的单独的分段集群中测试升级。

恭喜,您已成功将 AKS Engine 集群升级到版本1.17.0-beta.1。在下一节中,您将学习如何在 Kubernetes 中进行操作系统打补丁。

OS 打补丁

为了确保集群和基础设施的最佳安全性,您必须确保在节点上运行具有最新补丁的操作系统。幸运的是,Kubernetes 在节点维护方面非常灵活。任何维护的一般方法,包括需要重新启动的 OS 补丁,如下所示:

  1. Cordon(标记节点为不可调度)节点并排空现有的 pod。

  2. 应用所需的更新并重新启动机器。

  3. Uncordon节点以使其再次可调度。

或者,如果您使用不可变基础设施方法,则必须通过创建新的打补丁机器并删除旧机器来扩展前面的步骤。例如,在 AKS Engine 中,如果您在节点池中使用自定义 VM 镜像,则此场景可能如下所示:

  1. 构建新版本的 VM 镜像。

  2. 更新 VMSS 的 VM 镜像(https://docs.microsoft.com/en-us/azure/virtual-machine-scale-sets/virtual-machine-scale-sets-upgrade-scale-set#update-the-os-image-for-your-scale-set),可能直接在 ARM 模板中进行。

  3. 对于 VMSS 中的每个 VM,依次执行以下操作:cordon和排空节点,将 VM 镜像设置为 VMSS 实例的最新版本,并uncordon节点。

如果您有兴趣为 AKS Engine Windows 节点创建自定义 VM 映像,您可以阅读以下构建过程的描述,该过程使用 Packer 和 Azure DevOps:github.com/Azure/aks-engine/blob/master/docs/topics/windows-vhd.md

为了维护 Windows 节点的手动程序,请执行以下步骤:

  1. 假设我们想要打补丁1754k8s010 Windows 节点。

  2. 使用名称获取1754k8s010节点的私有 IP 地址:

PS C:\src> az vm show -g <resourceGroupName> -n 1754k8s010 --show-details --query 'privateIps'
"10.240.0.35,10.240.0.36,10.240.0.37,10.240.0.38,10.240.0.39,10.240.0.40,10.240.0.41,10.240.0.42,10.240.0.43,10.240.0.44,10.240.0.45,10.240.0.46,10.240.0.47,10.240.0.48,10.240.0.49,10.240.0.50,10.240.0.51,10.240.0.52,10.240.0.53,10.240.0.54,10.240.0.55,10.240.0.56,10.240.0.57,10.240.0.58,10.240.0.59,10.240.0.60,10.240.0.61,10.240.0.62,10.240.0.63,10.240.0.64,10.240.0.65"
  1. 使用其中一个私有 IP 从本地5500端口通过主节点到 Windows 节点上的端口3389(RDP)创建 SSH 隧道:
ssh -L 5500:10.240.0.35:3389 azureuser@<dnsPrefix>.<azureLocation>.cloudapp.azure.com
  1. 在另一个 PowerShell 窗口中,通过隧道启动 RDP 会话:
mstsc /v:localhost:5500
  1. 提供您的 Windows 节点凭据(如 ApiModel)并连接。

  2. 等待控制台初始化。

  3. 现在,您已经准备好进行维护,但首先,我们需要排空节点(这也会首先使节点cordons)。在本地计算机上的新的 PowerShell 窗口中,执行以下命令:

PS C:\src> kubectl drain 1754k8s010
node/1754k8s010 cordoned
node/1754k8s010 drained
  1. 当节点被排空时,您可以开始维护程序。例如,您可以使用控制台中的sconfig.cmd实用程序手动应用更新:

  1. 选择选项6并选择要安装的更新:

  1. 等待安装结束并重新启动机器(如果需要)。

  2. 当节点完全重新启动后,您可以uncordon节点以使其再次可调度:

PS C:\src> kubectl uncordon 1754k8s010
node/1754k8s010 uncordoned

您的节点现在将在集群中再次完全正常运行。

或者,您可以考虑使用 Azure Update Management 来管理集群中操作系统的更新和打补丁。您可以在官方文档中阅读更多信息:docs.microsoft.com/en-us/azure/automation/automation-update-management

在最后一节中,我们将展示在 Kubernetes 中哪些组件需要额外配置,如果您的生产集群在 HTTP(S)网络代理后运行。

为 Docker 守护程序和 Kubernetes 配置网络代理

在企业环境中,使用 HTTP(S)网络代理连接到外部网络,特别是互联网,是一种常见做法。这需要对所有运行在代理后面的组件进行额外的配置成本-我们将简要概述 Kubernetes 中需要使代理意识到使用外部注册表的 Docker 镜像并将代理设置传播到容器的组件。

假设我们的代理地址如下:

  • http://proxy.example.com:8080/ 用于 HTTP 代理

  • http://proxy.example.com:9090/ 用于 HTTPS 代理

其他标准代理的配置,例如 SFTP,可以类似地完成。您可能还需要适当的 no-proxy 变量来排除 Kubernetes 节点和本地网络,否则,您将无法在节点之间通信,或者流量将通过代理额外路由!现在,对于 Linux 节点和主节点(假设是基于 Debian 的发行版,如 Ubuntu),您需要确保配置以下设置:

  1. 为默认环境/etc/environment定义代理设置。这将使 APT 等工具遵守代理设置:
HTTP_PROXY=http://proxy.example.com:8080/
HTTPS_PROXY=http://proxy.example.com:9090/
http_proxy=http://proxy.example.com:8080/
https_proxy=http://proxy.example.com:9090/
  1. 为 Docker 守护程序环境设置代理。这将确保容器也接收代理变量。您可以使用以下内容定义/etc/systemd/system/docker.service.d/http-proxy.conf文件:
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:8080/" "HTTPS_PROXY=http://proxy.example.com:9090/" 
  1. 在代理后面的机器上构建 Docker 镜像时,考虑将代理设置作为参数传递:
docker build --build-arg http_proxy=http://proxy.example.com:8080/ \
 --build-arg https_proxy=http://proxy.example.com:9090/ \
 -t someimage .

对于 Windows 工作节点,您可以采取以下步骤:

  1. 以管理员身份从 PowerShell 定义全局环境变量:
[Environment]::SetEnvironmentVariable("HTTP_PROXY", "http://proxy.example.com:8080/", [EnvironmentVariableTarget]::Machine)
[Environment]::SetEnvironmentVariable("HTTPS_PROXY", "http://proxy.example.com:9090/", [EnvironmentVariableTarget]::Machine)
  1. 另外,确保使用 Web 浏览器引擎的任何流量也遵守代理设置:
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyServer /t REG_SZ /d proxy.example.com:8080 /f
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnable /t REG_DWORD /d 1 /f

这些配置步骤应该让您拉取 Docker 镜像,引导集群,并在连接到外部网络时使 pod 容器意识到这些设置。

总结

在本章中,我们重点介绍了在生产环境中运行 Kubernetes 集群的常见操作最佳实践。首先,我们介绍了为 Kubernetes 提供基础设施和可重复部署集群的方法-我们介绍了基础设施即代码不可变基础设施的概念,并展示了它们如何适用于 Kubernetes 领域。此外,我们提供了有关为基础设施和集群部署提供最佳工具的建议。接下来,您将了解 GitOps 是什么,以及如何使用 Flux 和 Git 存储库应用这一理念。我们重点关注了升级和修补底层集群基础设施和 Kubernetes 本身的操作方面。最后,您将学习如何确保您的 Kubernetes 集群可以在企业环境中在 HTTP(S)网络代理后运行。

恭喜!这是一个漫长的旅程,进入(几乎)未知的 Windows Kubernetes 领域-祝您在进一步的 Kubernetes 旅程中好运,并感谢您的阅读。

问题

  1. 基础设施即代码不可变基础设施之间有什么区别?

  2. 为什么 Kubernetes 被认为是使用基础设施即代码方法的平台?

  3. 什么是 GitOps?

  4. 在您的 Kubernetes 集群中使用 Flux 的好处是什么?

  5. 升级 Kubernetes 集群版本的步骤是什么?

  6. 对 Kubernetes 节点执行维护的程序是什么?

您可以在本书的评估中找到这些问题的答案。

进一步阅读

第十七章:评估

第一章:创建容器

  1. 对象命名空间、进程表、作业对象和 Windows 容器隔离文件系统。此外,在这些低级功能之上,主机计算服务HCS)和主机网络服务HNS)抽象了运行和管理容器的公共接口。

  2. Windows Server 容器要求主机操作系统版本与容器基础镜像操作系统版本匹配。此外,在 Windows 上,您可以使用 Hyper-V 隔离,这使得可以使用与基础镜像操作系统版本不匹配的容器运行。

  3. 在 Hyper-V 隔离中,每个容器都在一个专用的、最小的 Hyper-V 虚拟机中运行。容器不与主机操作系统共享内核;主机操作系统版本与容器基础操作系统版本之间没有兼容性限制。如果需要在非匹配的基础镜像操作系统版本和不受信任的代码执行场景中运行容器,则使用 Hyper-V 隔离。

  4. 要在 Docker Desktop(版本 18.02 或更高版本)中启用 LCOW 支持,必须在 Docker 设置|守护程序中启用实验性功能选项。创建一个 LCOW 容器需要为docker run命令指定--platform linux参数。

  5. docker logs <containerId>

  6. 对于已安装 Powershell 的 Windows 容器,可以使用以下命令:docker exec -it <containerId> powershell.exe

第二章:在容器中管理状态

  1. 容器层是每个 Docker 容器文件系统中的可写层的顶层。

  2. 绑定挂载提供了一个简单的功能,可以将容器主机中的任何文件或目录挂载到给定的容器中。卷提供了类似的功能,但它们完全由 Docker 管理,因此您不必担心容器主机文件系统中的物理路径。

  3. 可写容器层与容器主机耦合在一起,这意味着不可能轻松地将数据移动到不同的主机。层文件系统的性能比直接访问主机文件系统(例如使用卷)差。您不能在不同的容器之间共享可写层。

  4. 在 Windows 主机上使用 SMB 全局映射功能,可以将 SMB 共享挂载到容器中可见。然后,您可以将 SMB 共享在容器中作为主机机器上的常规目录挂载。

  5. 不行。要持久保存 Hyper-V 容器的存储数据,您必须使用 Docker 卷。如果您需要使用绑定挂载(例如,用于 SMB 全局映射),您必须使用进程隔离。

  6. docker volume prune

  7. Docker 中的卷驱动程序可用于管理托管在远程计算机或云服务上的卷。

第三章:使用容器映像

  1. Docker 注册表是一个有组织的、分层的系统,用于存储 Docker 映像,提供可伸缩的映像分发。Docker Hub 是由 Docker,Inc.托管和管理的官方公共 Docker 注册表。

  2. 标签是存储库中单个图像的版本标签。

  3. <dockerId>/<repositoryName>:<tag>

  4. Azure 容器注册表ACR)是由 Azure 云提供的完全托管的私有 Docker 注册表。在 ACR 的情况下,您可以使用自己的 Azure 存储帐户存储图像,并且可以使注册表完全私有,以满足您自己的基础设施需求。

  5. latest是在拉取或构建图像时使用的默认标签(如果您没有指定显式标签)。一般来说,除了在开发场景中,您不应该使用latest标签。在生产环境中,始终为您的 Kubernetes 清单或 Dockerfile 指令指定显式标签。

  6. Semver 建议使用三个数字的以下方案,即主要版本、次要版本和修补版本,用点分隔:<major>.<minor>.<patch>,根据需要递增每个数字。

  7. Docker 内容信任DCT)提供了一种验证数据数字签名的方法,该数据在 Docker 引擎和 Docker 注册表之间传输。此验证允许发布者对其图像进行签名,并且消费者(Docker 引擎)验证签名以确保图像的完整性和来源。

第四章:Kubernetes 概念和 Windows 支持

  1. 控制平面(主控)由一组组件组成,负责关于集群的全局决策,例如将应用实例的调度和部署到工作节点以及管理集群事件。数据平面由负责运行主控安排的容器工作负载的工作节点组成。

  2. 集群管理使用声明性模型执行,这使得 Kubernetes 非常强大 - 您描述所需的状态,Kubernetes 会完成所有繁重的工作,将集群的当前状态转换为所需的状态。

  3. Kubernetes Pod 由一个或多个共享内核命名空间、IPC、网络堆栈(因此您可以通过相同的集群 IP 地址对其进行寻址,并且它们可以通过本地主机进行通信)和存储的容器组成。换句话说,Pod 可以包含共享某些资源的多个容器。

  4. Deployment API 对象用于声明式管理 ReplicaSet 的部署和扩展。这是确保新版本应用平稳部署的关键 API 对象。

  5. Windows 机器只能作为工作节点加入集群。无法在 Windows 上运行主组件,也没有在混合 Linux/Windows 集群的本地 Kubernetes 开发环境的设置,目前没有标准解决方案,例如 Minikube 或 Docker Desktop for Windows 支持这样的配置。

  6. Minikube 旨在为 Kubernetes 的本地开发提供稳定的环境。它可用于 Windows、Linux 和 macOS,但只能提供 Linux 集群。

  7. AKSAzure Kubernetes Service的缩写)是 Azure 提供的完全托管的 Kubernetes 集群。AKS Engine 是 Azure 官方的开源工具,用于在 Azure 上提供自管理的 Kubernetes 集群。在内部,AKS 使用 AKS Engine,但它们不能管理彼此创建的集群。

第五章:Kubernetes 网络

  1. 运行在节点上的 Pod 必须能够与所有节点上的所有 Pod(包括 Pod 的节点)进行通信,而无需 NAT 和显式端口映射。例如,运行在节点上的所有 Kubernetes 组件,如 kubelet 或系统守护程序/服务,必须能够与该节点上的所有 Pod 进行通信。

  2. 您可以在集群节点之间存在二层(L2)连接时,仅使用 host-gw 来使用 Flannel。换句话说,在节点之间不能有任何 L3 路由器。

  3. NodePort 服务是作为 ClusterIP 服务实现的,具有使用任何集群节点 IP 地址和指定端口可达的额外功能。为实现这一点,kube-proxy 在 30000-32767 范围内(可配置)的每个节点上公开相同的端口,并设置转发,以便将对该端口的任何连接转发到 ClusterIP。

  4. 降低成本(您只使用一个云负载均衡器来提供传入流量)和 L7 负载均衡功能

  5. 容器运行时使用 CNI 插件将容器连接到网络,并在需要时从网络中移除它们。

  6. 内部 vSwitch 未连接到容器主机上的网络适配器,而外部 vSwitch 连接并提供与外部网络的连接。

  7. Docker 网络模式(驱动程序)是来自 Docker 的概念,是容器网络模型CNM)的一部分。这个规范是 Docker 提出的,旨在以模块化、可插拔的方式解决容器网络设置和管理挑战。CNI 是一个 CNCF 项目,旨在为任何容器运行时和网络实现提供一个简单明了的接口。它们以不同的方式解决了几乎相同的问题。在 Windows 上,Docker 网络模式和 CNI 插件的实现是相同的——它们都是 HNS 的轻量级适配器。

  8. 在 Windows 上,覆盖网络模式使用外部 Hyper-V vSwitch 创建一个 VXLAN 覆盖网络。每个覆盖网络都有自己的 IP 子网,由可定制的 IP 前缀确定。

第六章:与 Kubernetes 集群交互

  1. kubectl 使用位于~\.kube\config的 kubeconfig 文件。这个 YAML 配置文件包含了 kubectl 连接到 Kubernetes API 所需的所有参数。

  2. 您可以使用KUBECONFIG环境变量或--kubeconfig标志来强制 kubectl 在个别命令中使用不同的 kubeconfig。

  3. 上下文用于组织和协调对多个 Kubernetes 集群的访问。

  4. kubectl create是一个命令,用于创建新的 API 资源,而kubectl apply是一个声明性管理命令,用于管理 API 资源。

  5. kubectl patch通过合并当前资源状态和仅包含修改属性的补丁来更新资源。补丁的常见用例是在混合 Linux/Windows 集群中需要强制执行现有 DaemonSet 的节点选择器时。

  6. kubectl logs <podName>

  7. kubectl cp <podName>:<sourceRemotePath> <destinationLocalPath>

第七章:部署混合本地 Kubernetes 集群

  1. 如果您只计划将集群用于本地开发,请使用内部 NAT Hyper-V vSwitch。任何外部入站通信(除了您的 Hyper-V 主机机器)都需要 NAT。如果您的网络有 DHCP 和 DNS 服务器,您(或网络管理员)可以管理,那么请使用外部 Hyper-V vSwitch。这在大多数生产部署中都是这样的情况。

  2. 简而言之,更改操作系统配置,比如禁用交换空间,安装 Docker 容器运行时,安装 Kubernetes 软件包,执行kubeadm init

  3. 服务子网是一个虚拟子网(不可路由),用于 Pod 访问服务。虚拟 IP 的可路由地址转换由运行在节点上的 kube-proxy 执行。Pod 子网是集群中所有 Pod 使用的全局子网。

  4. kubeadm token create --print-join-command

  5. kubectl taint nodes --all node-role.kubernetes.io/master-

  6. Flannel 网络使用 host-gw 后端(在 Windows 节点上使用 win-bridge CNI 插件):host-gw 后端更可取,因为它处于稳定的功能状态,而 overlay 后端对于 Windows 节点仍处于 alpha 功能状态。

  7. 简而言之,下载sig-windows-tools脚本,安装 Docker 和 Kubernetes 软件包;为脚本准备 JSON 配置文件;并执行它们。

  8. kubectl logs <podName>

第八章:部署混合 Azure Kubernetes 服务引擎集群

  1. AKS 是 Azure 提供的一个完全托管的 Kubernetes 集群。AKS Engine 是 Azure 官方的开源工具,用于在 Azure 上为自管理的 Kubernetes 集群进行配置。在内部,AKS 使用 AKS Engine,但它们不能管理彼此创建的集群。

  2. AKS Engine 根据提供的配置文件(集群 apimodel)生成Azure 资源管理器(ARM)模板。然后,您可以使用此 ARM 模板在 Azure 基础架构上部署一个完全功能的自管理 Kubernetes 集群。

  3. 不可以。即使 AKS 在内部使用 AKS Engine,也不能使用 AKS Engine 来管理 AKS,反之亦然。

  4. Azure CLI,Azure Cloud Shell,kubectl,以及如果您想要使用 SSH 连接到节点,则还需要 Windows 的 SSH 客户端。

  5. AKS Engine 使用 apimodel(或集群定义)JSON 文件生成 ARM 模板,可用于直接部署 Kubernetes 集群到 Azure。

  6. 使用 SSH 并执行以下命令:ssh azureuser@<dnsPrefix>.<azureLocation>.cloudapp.azure.com

  7. 假设10.240.0.4是 Windows 节点的私有 IP,创建一个 SSH 连接到主节点,将 RDP 端口转发到 Windows 节点,使用ssh -L 5500:10.240.0.4:3389 azureuser@<dnsPrefix>.<azureLocation>.cloudapp.azure.com命令。在一个新的命令行窗口中,使用mstsc /v:localhost:5500命令启动一个 RDP 会话。

第九章:部署您的第一个应用程序

  1. 命令式方法包括执行命令式的 kubectl 命令,例如kubectl runkubectl expose。在声明性方法中,您始终修改对象配置(清单文件),并使用kubectl apply命令在集群中创建或更新它们(或者,您可以使用 Kustomization 文件)。

  2. 命令式的kubectl delete命令优于声明式删除,因为它提供可预测的结果。

  3. kubectl diff -f <file/directory>

  4. 推荐的做法是使用nodeSelector来可预测地调度您的 Pod,无论是 Windows 还是 Linux 容器。

  5. 您可以使用kubectl proxy访问任何 Service API 对象。kubectl port-forward是一个更低级别的命令,您可以使用它来访问单个 Pod 或部署中运行的 Pod 或服务后面的 Pod。

  6. 只有当您有能够运行 Ingress Controller Pods 的节点时,才可以使用 Ingress Controller。例如,对于 ingress-nginx,只有 Linux 节点才能部署 Ingress Controller,您将能够为运行在 Windows 节点上的服务创建 Ingress 对象,但所有负载均衡都将在 Linux 节点上执行。

  7. kubectl scale deployment/<deploymentName> --replicas=<targetNumberOfReplicas>

第十章:部署 Microsoft SQL Server 2019 和 ASP.NET MVC 应用程序

  1. 您可以从以下选项中选择:将参数传递给容器命令,为容器定义系统环境变量,将 ConfigMaps 或 Secrets 挂载为容器卷,并可选择使用 PodPresets 将所有内容包装起来。

  2. LogMonitor.exe充当应用程序进程的监督者,并将日志打印到标准输出,这些日志是根据配置文件从不同来源收集的。计划进一步扩展此解决方案,以用于侧车容器模式。

  3. 您需要确保迁移可以回滚,并且数据库架构与旧版本和新版本的应用程序完全兼容。换句话说,不兼容的更改(例如重命名)必须特别处理,以使各个步骤之间保持向后兼容。

  4. 这可以确保在 Pod 终止时数据持久性,并确保 SQL Server 故障转移,即使新的 Pod 被调度到不同的节点上。

  5. 您需要使用ef6.exe命令来应用迁移。这可以使用 Kubernetes Job 对象执行。

  6. 如果您为资源使用低于“限制”值的“请求”值,您可能会进入资源超额分配状态。这使得 Pod 可以临时使用比它们请求的资源更多的资源,并实现了更有效的 Pod 工作负载的装箱。

  7. VS 远程调试器在容器中的4020 TCP 端口上公开。要连接到它,而不将其公开为服务对象,您需要使用 kubectl 端口转发。

第十一章:配置应用程序以使用 Kubernetes 功能

  1. 命名空间的一般原则是提供资源配额和对象名称的范围。您将根据集群的大小和团队的大小来组织命名空间。

  2. 就绪探针用于确定给定容器是否准备好接受流量。存活探针用于检测容器是否需要重新启动。

  3. 这个探针的错误配置可能导致您的服务和容器重新启动循环中的级联故障。

  4. requests指定系统提供的给定资源的保证数量。limits指定系统提供的给定资源的最大数量。

  5. 避免抖动(副本计数频繁波动)。

  6. ConfigMaps 和 Secrets 可以容纳技术上任何类型的由键值对组成的数据。Secrets 的目的是保留访问依赖项的敏感信息,而 ConfigMaps 应该用于一般应用程序配置目的。

  7. volumeClaimTemplates用于为此 StatefulSet 中的每个 Pod 副本创建专用的 PersistentVolumeClaim。

  8. 为了确保在 Kubernetes 中对部署进行真正的零停机更新,您需要配置适当的探针,特别是就绪探针。这样,用户只有在该副本能够正确响应请求时才会被重定向到副本。

  9. 最小权限原则:您的应用程序应仅访问其自己的资源(建议您使用专用服务帐户运行每个应用程序,该帐户可以访问该应用程序的 Secrets 或 ConfigMaps),用户应根据其在项目中的角色拥有受限制的访问权限(例如,QA 工程师可能只需要对集群具有只读访问权限)。

第十二章:使用 Kubernetes 进行开发工作流程

  1. Helm 用于为您的 Kubernetes 应用程序创建可再分发的软件包。您可以使用它来部署其他人提供的应用程序,也可以将其用作您自己应用程序的内部软件包和微服务系统的依赖管理器。

  2. Helm 2 需要在 Kubernetes 上部署一个名为 Tiller 的专用服务,它负责与 Kubernetes API 的实际通信。这引起了各种问题,包括安全和 RBAC 问题。从 Helm 3.0.0 开始,不再需要 Tiller,图表管理由客户端完成。

  3. 在 Helm 中使用 Kubernetes Job 对象作为安装后钩子。

  4. 在 Helm 图表清单或值文件中使用新的 Docker 镜像,并执行 helm upgrade

  5. 快照调试器是 Azure 应用程序洞察的一个功能,它监视您的应用程序的异常遥测,包括生产场景。每当出现未处理的异常(顶部抛出),快照调试器都会收集托管内存转储,可以直接在 Azure 门户中进行分析,或者对于更高级的场景,可以使用 Visual Studio 2019 企业版。

  6. 您应该更喜欢 Kubernetes 的适当声明式管理。

  7. Azure Dev Spaces 服务为使用 AKS 集群的团队提供了快速迭代的开发体验。

第十三章:保护 Kubernetes 集群和应用程序

  1. Kubernetes 本身不提供管理访问集群的普通外部用户的手段。这应该委托给一个可以与 Kubernetes 集成的外部身份验证提供程序,例如,通过认证代理。

  2. 为了减少攻击向量,建议的做法是永远不要使用 LoadBalancer 服务公开 Kubernetes 仪表板,并始终使用 kubectl 代理来访问页面。

  3. 这将为您的 API 资源和 Secrets 提供额外的安全层,否则它们将以未加密的形式保存在 etcd 中。

  4. 不,此功能仅在 Linux 容器中受支持。

  5. NetworkPolicy 对象定义了 Pod 组如何相互通信以及一般网络端点的通信方式 - 将它们视为 OSI 模型第 3 层的网络分割的基本防火墙。要使用网络策略,您需要使用支持网络策略的网络提供程序之一。

  6. 在 Windows 上,作为卷挂载到 Pods 的 Kubernetes Secrets 以明文形式写入节点磁盘存储(而不是 RAM)。这是因为 Windows 目前不支持将内存文件系统挂载到 Pod 容器。这可能带来安全风险,并需要额外的操作来保护集群。

  7. 当您拥有根权限时,您可以从/proc/<pid>/environ枚举出进程的所有环境变量,包括以这种方式注入的 Secrets。对于作为卷挂载的 Secrets,由于使用了tmpfs,这是不可能的。

第十四章:使用 Prometheus 监控 Kubernetes 应用程序

  1. 为您的组件提供可观察性意味着公开有关其内部状态的信息,以便您可以轻松访问数据并推断出组件的实际状态。换句话说,如果某物是可观察的,您就可以理解它。

  2. WMI Exporter 可用于监视 Windows 节点主机操作系统和硬件。要监视 Docker 引擎本身,可以使用引擎公开的实验性指标服务器。

  3. 在大规模运行的生产环境中,您可以使用 Prometheus Operator 轻松部署和管理多个不同需求的 Prometheus 集群。

  4. WMI Exporter 和 Docker 引擎指标服务器在每个节点上的专用端口上公开指标。我们需要两个额外的抓取作业来单独处理它们。

  5. 将 Telegraf 服务直接托管在您的容器中使用。

  6. 为您的应用程序提供额外的仪器和对业务逻辑的洞察。

  7. 在您的服务对象清单中,定义一个额外的注释,例如prometheus.io/secondary-port。之后,您必须创建一个专用的抓取作业,它将以类似的方式消耗新的注释,就像prometheus.io/port一样。

  8. 热图是可视化直方图随时间变化的最有效方式,最近 Grafana 已扩展了对 Prometheus 直方图指标的热图本地支持。

第十五章:灾难恢复

  1. DR 和 BC 之间的主要区别在于,DR 侧重于在停机后使基础设施恢复运行,而 BC 涵盖了在重大事件期间保持业务场景运行。

  2. etcd集群由主节点和持久卷使用。

  3. 快照是由 etcd 的 v3 API 提供的备份文件。

  4. Velero 可以执行etcd快照,将其管理在外部存储中,并在需要时进行恢复。此外,它还可以用于使用 Restic 集成执行持久卷的备份。Etcd-operator 用于在 Kubernetes 之上提供多个etcd集群。您可以轻松管理etcd集群并执行备份恢复操作。如果您计划在您的环境中管理多个 Kubernetes 集群,请使用此方法。

  5. 访问所有 Kubernetes 主节点,并在所有机器上并行执行相同的步骤:下载目标快照文件,将其恢复到本地目录,停止 Kubernetes 主组件,停止etcd服务,停止 kubelet 服务,交换etcd数据目录,启动etcd服务,最后启动 kubelet 服务。

  6. Kubernetes CronJob 使您能够按固定计划安排 Kubernetes 作业,类似于 Linux 系统中的 cron。

  7. 从集群中删除失败的成员,添加新的替代成员,如果有多个失败的成员,则依次替换成员。

第十六章:运行 Kubernetes 的生产考虑

  1. 在不可变基础设施中,一旦机器被配置,您还不会执行任何修改。如果您需要进行配置更改或热修复,您需要构建新的机器映像并配置新的机器。

  2. Kubernetes 可以被视为管理您的不可变容器基础设施和应用程序工作负载的平台——每当您创建一个新的 Docker 映像并部署新版本时,您只是创建新的容器并丢弃旧的容器。如果您使用声明性方法来管理您的 Kubernetes 对象,您最终会得到整洁的基础设施即代码。

  3. GitOps 是一种由 WeaveWorks 提出的管理 Kubernetes 集群和应用程序的方式,其中 Git 存储库是声明性基础架构和应用程序工作负载的唯一真相来源。这种方法完全符合基础设施即代码范式。

  4. Flux 可以用于轻松实现 GitOps,用于您的 Kubernetes 集群。

  5. 升级运行在主主节点上的组件,升级运行在其他主节点上的组件,以及升级工作节点。

  6. 将节点标记为不可调度,并排空现有的 Pod,然后应用所需的更新并重新启动机器,然后取消节点标记,使其再次可调度。

posted @ 2024-05-20 12:00  绝不原创的飞龙  阅读(14)  评论(0编辑  收藏  举报