Kubernetes-Azure-实用指南第二版(全)

Kubernetes Azure 实用指南第二版(全)

原文:zh.annas-archive.org/md5/8F91550A7983115FCFE36001051EE26C

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

关于

本节简要介绍了作者、本书的内容涵盖范围、您需要开始的技术技能,以及完成所有包含的活动和练习所需的硬件和软件要求。

关于在 Azure 上实践 Kubernetes,第二版

Kubernetes 是容器编排的领先标准,被初创企业和大型企业广泛使用。微软是这个开源项目的最大贡献者之一,并提供托管服务以规模运行 Kubernetes 集群。

本书将带领您了解在Azure Kubernetes 服务AKS)上构建和运行应用程序所需的步骤。它从解释 Docker 和 Kubernetes 的基础知识开始,然后您将构建一个集群并开始部署多个应用程序。借助真实世界的例子,您将学习如何在 AKS 上部署应用程序,实现身份验证,监视您的应用程序,并将 AKS 与其他 Azure 服务(如数据库、事件中心和函数)集成。

通过本书,您将熟练掌握在 Azure 上运行 Kubernetes 和利用部署所需工具的能力。

关于作者

尼尔斯·弗兰森斯是一位技术爱好者,也是多个开源技术的专家。自 2013 年以来,他一直在使用公共云技术。

在他目前的职位上,作为微软的高级云解决方案架构师,他与微软的战略客户合作进行云采用。他已经帮助多个客户迁移到 Azure。其中一个迁移是将一个重要的公共网站迁移到 Kubernetes 并重新平台化。

在 Kubernetes 之外,尼尔斯的专业领域是 Azure 中的网络和存储。

他拥有比利时安特卫普大学的工程硕士学位。

当他不工作的时候,你可以发现尼尔斯和他的妻子凯利和朋友们一起玩桌游,或者在加利福尼亚州圣何塞的许多小径中跑步。

冈瑟·伦兹是 Varian 技术办公室的高级总监。他是一位富有创新精神的软件研发领导者、架构师、MBA、出版作者、公共演讲者,以及具有 20 多年经验的战略技术远见者。

他拥有成功领导 50 多人的大型、创新和变革性软件开发和 DevOps 团队的成功记录,重点是持续改进。

他通过利用颠覆性的流程、工具和技术,如云、DevOps、精益/敏捷、微服务架构、数字转型、软件平台、人工智能和分布式机器学习,定义并领导了整个软件产品生命周期中的分布式团队。

他曾获得微软最有价值专家-软件架构奖(2005-2008 年)。

Gunther 已出版两本书,《.NET-完整的开发周期》和《.NET 中的实用软件工厂》。

Shivakumar Gopalakrishnan是 Varian Medical Systems 的 DevOps 架构师。他已经向 Varian 产品开发引入了 Docker、Kubernetes 和其他云原生工具,以实现“一切皆代码”。

他在各种领域拥有多年的软件开发经验,包括网络、存储、医学成像,目前是 DevOps。他致力于开发可扩展的存储设备,专门针对医学成像需求进行调整,并帮助架构云原生解决方案,用于提供由微服务支持的模块化 AngularJS 应用程序。他曾在多个活动上发表讲话,介绍在 DevOps 中整合人工智能和机器学习,以实现大型企业学习文化。

他帮助高度受监管的大型医疗企业团队采用现代敏捷/DevOps 方法,包括“你构建它,你运行它”的模式。他已经定义并领导了一个 DevOps 路线图,将传统团队转变为无缝采用安全和质量优先方法的团队,使用 CI/CD 工具。

他拥有印度古因迪工程学院的工程学学士学位,以及马里兰大学帕克分校的理学硕士学位。

学习目标

通过本书,您将能够:

  • 了解 Docker 和 Kubernetes 的基本原理

  • 设置 AKS 集群

  • 将应用程序部署到 AKS

  • 监视 AKS 上的应用程序并处理常见故障

  • 为 AKS 上的应用程序设置身份验证

  • 将 AKS 与 Azure Database for MySQL 集成

  • 从 AKS 中的应用程序利用 Azure 事件中心

  • 保护您的集群

  • 将无服务器函数部署到您的集群

受众

如果您是云工程师、云解决方案提供商、系统管理员、站点可靠性工程师或对 DevOps 感兴趣,并且正在寻找在 Azure 环境中运行 Kubernetes 的广泛指南,那么本书适合您。

方法

本书提供了实用和理论知识的结合。它涵盖了引人入胜的现实场景,展示了基于 Kubernetes 的应用程序如何在 Azure 平台上运行。每一章都旨在使您能够在实际环境中应用所学到的一切。在第一章和第二章之后,每一章都是独立的,可以独立于之前的章节运行。

软件要求

我们还建议您提前安装以下软件:

  • 具有 Linux、Windows 10 或 macOS 操作系统的计算机

  • 互联网连接和网络浏览器,以便您可以连接到 Azure

本书中的所有示例都经过设计,可以在 Azure Cloud Shell 中使用。您无需在计算机上安装额外的软件。

约定

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:

“以下代码片段将使用kubectl命令行工具来创建在文件guestbook-all-in-one.yaml中定义的应用程序。”

以下是一个代码块示例:

kubectl create -f guestbook-all-in-one.yaml

我们将使用反斜杠\来表示代码将跨越书中多行。您可以复制反斜杠并继续到下一行,或者忽略反斜杠并在单行上键入完整的多行代码。例如:

az aks nodepool update --disable-cluster-autoscaler \
-g rg-handsonaks --cluster-name handsonaks --name agentpool

在许多情况下,我们使用了尖括号<>。您需要用实际参数替换这些内容,并且不要在命令中使用这些括号。

下载资源

本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Hands-On-Kubernetes-on-Azure---Second-Edition。您可以在此书中找到使用的 YAML 和其他文件,在相关实例中引用。

我们还有来自我们丰富的图书和视频目录的其他代码包,可在github.com/PacktPublishing/上找到。去看看吧!

第一部分:基础知识

在本书的第一部分中,我们将涵盖您需要了解的基本概念,以便跟随本书中的示例。

我们将从解释这些基本概念开始,比如 Docker 和 Kubernetes。然后,我们将解释如何在 Azure 上创建一个 Kubernetes 集群并部署一个示例应用程序。

当您完成本节时,您将具有关于 Docker 和 Kubernetes 的基础知识,并且在 Azure 中运行一个 Kubernetes 集群,这将使您能够跟随本书中的示例。

本节包括以下章节:

  • 第一章,Docker 和 Kubernetes 简介

  • 第二章,Azure 上的 Kubernetes(AKS)

第一章:Docker 和 Kubernetes 简介

Kubernetes 已成为容器编排的领先标准。自 2014 年成立以来,它已经获得了巨大的流行。它已被初创公司和主要企业采用,而且主要的公共云供应商都提供了托管的 Kubernetes 服务。

Kubernetes 建立在 Docker 容器革命的成功基础上。Docker 既是一家公司,也是一种技术的名称。作为一种技术,Docker 是创建和运行软件容器的标准方式,通常称为 Docker 容器。容器本身是一种打包软件的方式,使得在任何平台上运行该软件变得容易,从您的笔记本电脑到数据中心的服务器,再到公共云中运行的集群。

Docker 也是 Docker 技术背后公司的名称。尽管核心技术是开源的,但 Docker 公司专注于通过多种商业产品减少开发人员的复杂性。

Kubernetes 将 Docker 容器提升到了一个新的水平。Kubernetes 是一个容器编排器。容器编排器是一种软件平台,可以轻松地在成千上万台机器上运行成千上万个容器。它自动化了部署、运行和扩展应用程序所需的许多手动任务。编排器将负责安排正确的容器在正确的机器上运行,并负责健康监控和故障转移,以及扩展您部署的应用程序。

Docker 和 Kubernetes 都是开源软件项目。开源软件允许来自许多公司的开发人员共同合作开发单一软件。Kubernetes 本身有来自微软、谷歌、红帽、VMware 等公司的贡献者。

三大主要的公共云平台 - Azure,Amazon Web Services(AWS)和 Google Cloud Platform(GCP) - 都提供了托管的 Kubernetes 服务。这在市场上引起了很大的兴趣,因为这些托管服务的计算能力几乎是无限的,而且易于使用,可以轻松构建和部署大规模应用程序。

Azure Kubernetes Service(AKS)是 Azure 的 Kubernetes 托管服务。它管理了为您组合所有前述服务的复杂性。在本书中,您将学习如何使用 AKS 来运行您的应用程序。每一章都会介绍新的概念,您将通过本书中的许多示例来应用这些概念。

然而,作为一名工程师,了解支撑 AKS 的技术仍然非常有用。我们将在本章探讨这些基础知识。您将了解 Linux 进程及其与 Docker 的关系。您将看到各种进程如何很好地适配到 Docker 中,以及 Docker 如何很好地适配到 Kubernetes 中。尽管 Kubernetes 在技术上是一个容器运行时无关的平台,但 Docker 是最常用的容器技术,被广泛应用。

本章介绍了基本的 Docker 概念,以便您可以开始您的 Kubernetes 之旅。本章还简要介绍了将帮助您构建容器、实现集群、执行容器编排和在 AKS 上排除故障的基础知识。对本章内容的粗略了解将使构建在 AKS 上的经过认证、加密、高度可扩展的应用程序所需的大部分工作变得不再神秘。在接下来的章节中,您将逐渐构建可扩展和安全的应用程序。

本章将涵盖以下主题:

  • 将我们带到这里的软件演变

  • Docker 的基础知识

  • Kubernetes 的基础知识

  • AKS 的基础知识

本章的目的是介绍基本知识,而不是提供描述 Docker 和 Kubernetes 的详尽信息源。首先,我们将首先看一下软件是如何演变到现在的。

将我们带到这里的软件演变

有两个主要的软件开发演变使得 Docker 和 Kubernetes 变得流行。一个是采用了微服务架构风格。微服务允许应用程序由一系列小服务构建,每个服务都提供特定的功能。使 Docker 和 Kubernetes 变得流行的另一个演变是 DevOps。DevOps 是一组文化实践,允许人员、流程和工具更快、更频繁、更可靠地构建和发布软件。

尽管您可以在不使用微服务或 DevOps 的情况下使用 Docker 和 Kubernetes,但这些技术最广泛地被采用用于使用 DevOps 方法部署微服务。

在本节中,我们将讨论两种演变,首先是微服务。

微服务

软件开发随着时间的推移发生了巨大变化。最初,软件是在单一系统上开发和运行的,通常是在大型机上。客户端可以通过终端连接到大型机,而且只能通过那个终端。当计算机网络变得普遍时,这种情况发生了变化,客户端-服务器编程模型出现了。客户端可以远程连接到服务器,甚至在连接到服务器检索应用程序所需的部分数据时,在自己的系统上运行应用程序的一部分。

客户端-服务器编程模型已经发展成真正的分布式系统。分布式系统不同于传统的客户端-服务器模型,因为它们在多个不同的系统上运行多个不同的应用程序,并且彼此相互连接。

如今,在开发分布式系统时,微服务架构很常见。基于微服务的应用程序由一组服务组成,这些服务共同形成应用程序,而这些个别服务本身可以独立构建、测试、部署和独立扩展。这种风格有许多好处,但也有一些缺点。

微服务架构的一个关键部分是每个个别服务只提供一个核心功能。每个服务提供一个单一的业务功能。不同的服务共同形成完整的应用程序。这些服务通过网络通信共同工作,通常使用 HTTP REST API 或 gRPC。

这种架构方法通常被应用程序使用 Docker 和 Kubernetes 运行。Docker 被用作单个服务的打包格式,而 Kubernetes 是部署和管理一起运行的不同服务的编排器。

在我们深入研究 Docker 和 Kubernetes 的具体内容之前,让我们先探讨一下采用微服务的利与弊。

运行微服务的优势

运行基于微服务的应用程序有几个优势。第一个是每个服务都独立于其他服务。这些服务被设计得足够小(因此是微型),以满足业务领域的需求。由于它们很小,它们可以被制作成自包含的、可以独立测试的,因此可以独立发布。

这导致了每个微服务都可以独立扩展。如果应用程序的某个部分需求增加,该部分可以独立于应用程序的其他部分进行扩展。

服务可以独立扩展也意味着它们可以独立部署。在微服务方面有多种部署策略。最流行的是滚动升级和蓝/绿部署。

通过滚动升级,服务的新版本只部署到部分最终用户社区。如果服务正常,新版本会受到仔细监控,并逐渐获得更多的流量。如果出现问题,之前的版本仍在运行,流量可以轻松切换。

通过蓝/绿部署,您可以将服务的新版本独立部署。一旦部署并测试了服务的新版本,您就可以将 100%的生产流量切换到新版本。这可以实现服务版本之间的平稳过渡。

微服务架构的另一个好处是每个服务可以用不同的编程语言编写。这被描述为多语言 - 能够理解和使用多种语言。例如,前端服务可以使用流行的 JavaScript 框架开发,后端可以使用 C#开发,而机器学习算法可以使用 Python 开发。这使您可以为每个服务选择合适的语言,并让开发人员使用他们最熟悉的语言。

运行微服务的缺点

每个硬币都有两面,微服务也是如此。虽然基于微服务的架构有多个优点,但这种架构也有其缺点。

微服务的设计和架构需要高度的软件开发成熟度才能正确实施。深刻理解领域的架构师必须确保每个服务都是有界的,并且不同的服务是内聚的。由于服务彼此独立且独立版本化,因此这些不同服务之间的软件合同非常重要。

微服务设计的另一个常见问题是在监视和故障排除这样的应用程序时增加了复杂性。由于不同的服务构成单个应用程序,并且这些不同的服务在多个服务器上运行,因此记录和跟踪这样的应用程序是一项复杂的工作。

与前述的缺点相关的是,通常在微服务中,您需要为应用程序构建更多的容错能力。由于应用程序中不同服务的动态性质,故障更有可能发生。为了保证应用程序的可用性,重要的是在构成应用程序的不同微服务中构建容错能力。实施重试逻辑或断路器等模式对于避免单一故障导致应用程序停机至关重要。

通常与微服务相关联,但是一个独立的转型是 DevOps 运动。我们将在下一节探讨 DevOps 的含义。

DevOps

DevOps 字面上意味着开发和运营的结合。更具体地说,DevOps 是人员、流程和工具的结合,以更快、更频繁、更可靠地交付软件。DevOps 更多地涉及一套文化实践,而不是任何特定的工具或实施。通常,DevOps 涵盖软件开发的四个领域:规划、开发、发布和操作软件。

注意

存在许多关于 DevOps 的定义。作者采用了这个定义,但鼓励读者在 DevOps 的文献中探索不同的定义。

DevOps 文化始于规划。在 DevOps 项目的规划阶段,项目的目标被概述。这些目标在高层次(称为史诗)和较低层次(在特性任务中)都有概述。DevOps 项目中的不同工作项被记录在特性积压中。通常,DevOps 团队使用敏捷规划方法,在编程冲刺中工作。看板经常被用来表示项目状态和跟踪工作。当任务从待办状态变为进行中再到完成时,它在看板上从左向右移动。

当工作计划好后,实际的开发工作就可以开始了。在 DevOps 文化中,开发不仅仅是编写代码,还包括测试、审查和与团队成员集成。诸如 Git 之类的版本控制系统用于不同团队成员之间共享代码。自动化的持续集成(CI)工具用于自动化大部分手动任务,如测试和构建代码。

当一个功能完成编码、测试和构建后,就可以交付了。DevOps 项目的下一个阶段可以开始:交付。使用持续交付(CD)工具来自动化软件的部署。通常,软件会部署到不同的环境,如测试、质量保证或生产。使用自动化和手动门来确保在进入下一个环境之前的质量。

最后,当一款软件在生产中运行时,运维阶段就可以开始了。这个阶段涉及在生产中维护、监控和支持应用程序。最终目标是以尽可能少的停机时间可靠地运行应用程序。任何问题都应该尽可能被主动识别。软件中的错误需要在积压中被跟踪。

DevOps 流程是一个迭代的过程。一个团队永远不只处于一个阶段。整个团队不断地规划、开发、交付和操作软件。

存在多种工具来实施 DevOps 实践。有针对单个阶段的点解决方案,比如用于规划的 Jira 或用于 CI 和 CD 的 Jenkins,以及完整的 DevOps 平台,比如 GitLab。微软提供了两种解决方案,使客户能够采用 DevOps 实践:Azure DevOps 和 GitHub。Azure DevOps 是一套服务,支持 DevOps 流程的所有阶段。GitHub 是一个单独的平台,支持 DevOps 软件开发。GitHub 被认为是领先的开源软件开发平台,托管了超过 4000 万个开源项目。

微服务和 DevOps 通常与 Docker 和 Kubernetes 结合使用。在介绍了微服务和 DevOps 之后,我们将继续本章的第一部分,介绍 Docker 和容器的基础知识,然后是 Kubernetes 的基础知识。

Docker 容器的基础知识

自 20 世纪 70 年代以来,Linux 内核中一种容器技术已经存在。今天容器的技术,称为 cgroups,是由 Google 在 2006 年引入 Linux 内核的。Docker 公司在 2013 年通过引入一种简单的开发者工作流程使这项技术变得流行。该公司以自己的名字命名了这项技术,因此 Docker 这个名字既可以指公司,也可以指技术。不过,通常我们使用 Docker 来指代这项技术。

Docker 作为一种技术既是一种打包格式,也是一个容器运行时。我们将打包称为一种架构,允许应用程序与其依赖项(如二进制文件和运行时)一起打包。运行时指的是运行容器映像的实际过程。

你可以通过在 Docker Hub(hub.docker.com/)创建一个免费的 Docker 账户,并使用该登录信息打开 Docker 实验室(labs.play-with-docker.com/)来尝试 Docker。这将为你提供一个预先安装了 Docker 的环境,有效期为 4 小时。在本节中,我们将使用 Docker 实验室来构建我们自己的容器和镜像。

注意:

虽然在本章中我们使用基于浏览器的 Docker 实验室来介绍 Docker,但你也可以在本地桌面或服务器上安装 Docker。对于工作站,Docker 有一个名为 Docker Desktop 的产品(www.docker.com/products/docker-desktop),适用于 Windows 和 Mac,可以在本地创建 Docker 容器。在服务器上,无论是 Windows 还是 Linux,Docker 也可以作为容器的运行时使用。

Docker 镜像

Docker 使用镜像来启动一个新的容器。镜像包含了在容器内运行所需的所有软件。容器镜像可以存储在本地计算机上,也可以存储在容器注册表中。有公共注册表,如公共 Docker Hub(hub.docker.com/),也有私有注册表,如 Azure 容器注册表(ACR)。当你作为用户在 PC 上没有镜像时,你将使用docker pull命令从注册表中拉取镜像。

在下面的示例中,我们将从公共 Docker Hub 存储库中拉取一个镜像并运行实际的容器。你可以按照以下说明在 Docker 实验室中运行这个示例:

#First we will pull an image
docker pull docker/whalesay
#We can then look at which images we have locally
docker images
#Then we will run our container
docker run docker/whalesay cowsay boo

这些命令的输出将类似于图 1.1

从公共 Docker Hub 仓库中拉取的 Docker 镜像以及显示镜像 ID 和大小的输出。此外,运行 Docker 镜像会显示一张鲸鱼说“boo”的图片。

图 1.1:在 Docker 实验室中运行 Docker 的示例

这里发生的是,Docker 首先将您的镜像分成多个部分,并将其存储在运行的机器上。当我们运行实际的应用程序时,它使用本地镜像来启动一个容器。如果我们详细查看命令,您会发现docker pull只接受一个参数,即docker/whalesay。如果您没有提供私有容器注册表,Docker 将在公共 Docker Hub 中查找镜像,这就是 Docker 从中拉取我们的镜像的地方。docker run命令接受了几个参数。第一个参数是docker/whalesay,这是对镜像的引用。接下来的两个参数,cowsay boo,是传递给正在运行的容器以执行的命令。

在前面的示例中,我们了解到可以在不先构建镜像的情况下运行容器是可能的。然而,通常情况下,您会想要构建自己的镜像。为此,您可以使用Dockerfile。Dockerfile 包含 Docker 将遵循的步骤,从基础镜像开始构建您的镜像。这些指令可以包括添加文件、安装软件或设置网络等。下面提供了一个 Dockerfile 的示例,我们将在我们的 Docker playground 中创建:

FROM docker/whalesay:latest
RUN apt-get -y -qq update && apt-get install -qq -y fortunes
CMD /usr/games/fortune -a | cowsay

这个 Dockerfile 有三行。第一行将指示 Docker 使用哪个镜像作为新镜像的源镜像。下一步是运行一个命令,向我们的镜像添加新功能。在这种情况下,更新我们的apt仓库并安装一个叫做fortunes的应用程序。最后,CMD命令告诉 Docker 在基于这个镜像运行的容器中执行哪个命令。

通常将 Dockerfile 保存在名为Dockerfile的文件中,不带扩展名。要构建我们的镜像,您需要执行docker build命令,并指向您创建的 Dockerfile。在构建 Docker 镜像时,该过程将读取 Dockerfile 并执行其中的不同步骤。该命令还将输出运行容器和构建镜像所采取的步骤。让我们演示构建我们自己的镜像。

要创建这个 Dockerfile,通过vi Dockerfile命令打开一个文本编辑器。vi 是 Linux 命令行中的高级文本编辑器。如果你不熟悉它,让我们一起看看你该如何在其中输入文本:

  1. 打开 vi 后,按下i键进入插入模式。

  2. 然后,要么复制粘贴,要么输入这三行代码。

  3. 之后,按下Esc键,然后输入:wq!来写入(w)你的文件并退出(q)文本编辑器。

下一步是执行docker build来构建我们的镜像。我们将在该命令中添加最后一部分,即给我们的镜像添加一个标签,这样我们就可以用一个有用的名称来调用它。要构建你的镜像,你将使用docker build -t smartwhale .命令(不要忘记在这里加上最后的点)。

现在你会看到 Docker 执行一系列步骤 - 在我们的例子中是三个步骤 - 以构建我们的镜像。镜像构建完成后,你可以运行你的应用程序。要运行容器,你需要运行docker run smartwhale,然后你应该会看到类似于图 1.2的输出。然而,你可能会看到不同的智能引号。这是因为fortunes应用程序生成不同的引言。如果你多次运行容器,你会看到不同的引言出现,就像图 1.2中显示的那样。

运行 docker run smartwhale 命令的输出,显示由 fortunes 应用程序生成的智能引号。

图 1.2:运行自定义容器的示例

这就结束了我们对 Docker 的概述和演示。在本节中,你从一个现有的容器镜像开始,并在 Docker 实验室上启动了它。之后,你进一步构建了自己的容器镜像,并使用自己的镜像启动了容器。现在你已经学会了构建和运行容器的方法。在下一节中,我们将介绍 Kubernetes。Kubernetes 允许你以规模运行多个容器。

Kubernetes 作为容器编排平台

构建和运行单个容器似乎足够简单。然而,当你需要在多个服务器上运行多个容器时,情况可能会变得复杂。这就是容器编排器可以帮助的地方。容器编排器负责安排容器在服务器上运行,当容器失败时重新启动容器,当主机不健康时将容器移动到新主机,以及更多其他任务。

当前领先的编排平台是 Kubernetes(kubernetes.io/)。Kubernetes 受到了 Google 的 Borg 项目的启发,该项目本身在生产环境中运行了数百万个容器。

Kubernetes 采用声明性的编排方式;也就是说,你指定你需要什么,Kubernetes 负责部署你指定的工作负载。你不再需要手动启动这些容器,因为 Kubernetes 将启动你指定的 Docker 容器。

注意

尽管 Kubernetes 支持多个容器运行时,但 Docker 是最流行的运行时。

在本书中,我们将构建多个在 Kubernetes 中运行容器的示例,并且你将了解更多关于 Kubernetes 中不同对象的知识。在这个介绍性的章节中,我们将介绍 Kubernetes 中的三个基本对象,这些对象在每个应用中都可能会看到:Pod、Deployment 和 Service。

Kubernetes 中的 Pod

Kubernetes 中的 Pod 是基本的调度块。一个 Pod 是一个或多个容器的组合。这意味着一个 Pod 可以包含单个容器或多个容器。当创建一个只有一个容器的 Pod 时,你可以互换使用容器和 Pod 这两个术语。然而,Pod 这个术语仍然更受青睐。

当一个 Pod 包含多个容器时,这些容器共享相同的文件系统和网络命名空间。这意味着当一个 Pod 中的容器写入一个文件时,该 Pod 中的其他容器可以读取该文件。这也意味着 Pod 中的所有容器可以使用localhost网络相互通信。

在设计方面,你应该只将需要紧密集成的容器放在同一个 Pod 中。想象一下以下情况:你有一个不支持 HTTPS 的旧 Web 应用。你想要升级该应用以支持 HTTPS。你可以创建一个包含旧 Web 应用的 Pod,并包含另一个容器,该容器将为该应用执行 SSL 卸载,如图 1.3所述。用户将使用 HTTPS 连接到你的应用,而中间的容器将 HTTPS 流量转换为 HTTP:

一个执行 HTTPS 卸载的多容器 Pod 的示例,说明用户如何向 HTTPS 卸载容器发出 HTTPS 请求,然后该容器再向 Pod 内的旧 Web 应用发出 HTTP 请求。

图 1.3:执行 HTTPS 卸载的多容器 Pod 的示例

注意

这个设计原则被称为边车。微软提供了一本免费的电子书,描述了多个多容器 Pod 设计和设计分布式系统(azure.microsoft.com/resources/designing-distributed-systems/)。

一个 Pod,无论是单个还是多个容器的 Pod,都是一个短暂的资源。这意味着 Pod 可以在任何时候被终止,并在另一个节点上重新启动。当这种情况发生时,存储在该 Pod 中的状态将丢失。如果您需要在应用程序中存储状态,您需要将其存储在StatefulSet中,我们将在第三章在 AKS 上部署应用程序中讨论,或者将状态存储在 Kubernetes 之外的外部数据库中。

Kubernetes 中的部署

Kubernetes 中的部署提供了围绕 Pod 的功能层。它允许您从相同的定义中创建多个 Pod,并轻松地对部署的 Pod 进行更新。部署还有助于扩展您的应用程序,甚至可能自动扩展您的应用程序。

在底层,部署创建一个ReplicaSet,然后将创建您请求的 Pod。ReplicaSet是 Kubernetes 中的另一个对象。ReplicaSet的目的是在任何给定时间维护一组稳定运行的 Pod。如果您对部署进行更新,Kubernetes 将创建一个包含更新的 Pod 的新ReplicaSet。默认情况下,Kubernetes 将对新版本进行滚动升级。这意味着它将启动一些新的 Pod。如果这些 Pod 正常运行,那么它将终止一些旧的 Pod 并继续这个循环,直到只有新的 Pod 在运行。

部署、ReplicaSet 和 Pod 之间关系的图形表示。

图 1.4:部署、ReplicaSet 和 Pod 之间的关系

Kubernetes 中的服务

Kubernetes 中的服务是一个网络级的抽象。这允许您在单个 IP 地址和单个 DNS 名称下公开部署中的多个 Pod。

Kubernetes 中的每个 Pod 都有自己的私有 IP 地址。理论上,您可以使用这个私有 IP 地址连接您的应用程序。然而,正如前面提到的,Kubernetes Pods 是短暂的,这意味着它们可以被终止和移动,这会影响它们的 IP 地址。通过使用 Service,您可以使用单个 IP 地址将您的应用程序连接在一起。当一个 Pod 从一个节点移动到另一个节点时,Service 将确保流量被路由到正确的端点。

在本节中,我们介绍了 Kubernetes 和三个基本对象。在下一节中,我们将介绍 AKS。

Azure Kubernetes 服务

Azure Kubernetes 服务AKS)使创建和管理 Kubernetes 集群变得更容易。

典型的 Kubernetes 集群由多个主节点和多个工作节点组成。Kubernetes 中的节点相当于虚拟机VM)。主节点包含 Kubernetes API 和包含集群状态的数据库。工作节点是运行实际工作负载的虚拟机。

AKS 使创建集群变得更加容易。当您创建一个 AKS 集群时,AKS 会免费为您设置 Kubernetes 主节点。然后,AKS 将在您的订阅中创建虚拟机,并将这些虚拟机转换为您网络中 Kubernetes 集群的工作节点。您只需为这些虚拟机付费;您不需要为主节点付费。

在 AKS 中,Kubernetes 服务与 Azure 负载均衡器集成,Kubernetes Ingress 与应用程序网关集成。Azure 负载均衡器是一个第 4 层网络负载均衡器服务;应用程序网关是一个基于 HTTP 的第 7 层负载均衡器。Kubernetes 和这两个服务之间的集成意味着当您在 Kubernetes 中创建一个服务或 Ingress 时,Kubernetes 将在 Azure 负载均衡器或应用程序网关中分别创建一个规则。Azure 负载均衡器或应用程序网关将然后将流量路由到托管您的 Pod 的集群中的正确节点。

此外,AKS 添加了许多功能,使得更容易管理集群。AKS 包含升级集群到更新的 Kubernetes 版本的逻辑。它还可以轻松地扩展您的集群,使其变得更大或更小。

服务还带有使操作更容易的集成。AKS 集群预先配置了与 Azure Active Directory(Azure AD)的集成,以便简化身份管理和基于角色的访问控制(RBAC)。RBAC 是定义哪些用户可以访问资源以及他们可以对这些资源执行哪些操作的配置过程。AKS 还集成到 Azure Monitor for containers 中,这使得监视和排除故障变得更简单。

总结

在本章中,我们介绍了 Docker 和 Kubernetes 的概念。我们运行了许多容器,从现有的镜像开始,然后使用我们自己构建的镜像。在演示之后,我们探索了三个基本的 Kubernetes 对象:Pod、Deployment 和 Service。

这为剩余的章节提供了共同的背景,您将在 Microsoft AKS 中部署 Docker 化的应用程序。您将看到,Microsoft 的 AKS 平台即服务(PaaS)提供简化部署,通过处理许多管理和运营任务,您将不得不自行处理这些任务,如果您管理和运营自己的 Kubernetes 基础设施。

在下一章中,我们将介绍 Azure 门户及其组件,以便在创建您的第一个 AKS 集群的情况下使用。

第二章:在 Azure 上使用 Kubernetes(AKS)

正确和安全地安装和维护 Kubernetes 集群是困难的。幸运的是,所有主要的云提供商,如 Azure、AWS 和 Google Cloud Platform(GCP),都可以帮助安装和维护集群。在本章中,您将通过 Azure 门户导航,启动自己的集群,并运行一个示例应用程序。所有这些都将在您的浏览器中完成。

本章将涵盖以下主题:

  • 创建新的 Azure 免费帐户

  • 导航 Azure 门户

  • 启动您的第一个集群

  • 启动您的第一个应用程序

让我们首先看看创建 AKS 集群的不同方式,然后运行我们的示例应用程序。

部署 AKS 集群的不同方式

本章将介绍部署 AKS 集群的图形化方式。然而,有多种方法可以创建您的 AKS 集群:

  • 使用门户:门户为您提供了通过向导部署集群的图形化方式。这是部署您的第一个集群的好方法。对于多个部署或自动化部署,建议使用以下方法之一。

  • 使用 Azure CLI:Azure 命令行界面CLI)是一个用于管理 Azure 资源的跨平台 CLI。这使您可以编写脚本来部署您的集群,可以集成到其他脚本中。

  • 使用 Azure PowerShell:Azure PowerShell 是一组用于直接从 PowerShell 管理 Azure 资源的 PowerShell 命令。它也可以用于创建 Kubernetes 集群。

  • 使用 ARM 模板Azure 资源管理器ARM)模板是 Azure 本地的基础设施即代码IaC)语言。它允许您声明性地部署您的集群。这使您可以创建一个可以被多个团队重复使用的模板。

  • 使用 Terraform 部署 Azure:Terraform 是由 HashiCorp 开发的开源 IaC 工具。该工具在开源社区中非常受欢迎,可用于部署云资源,包括 AKS。与 ARM 模板一样,Terraform 也使用声明性模板来创建您的集群。

在本章中,我们将使用 Azure 门户创建我们的集群。如果您有兴趣使用 CLI、ARM 或 Terraform 部署集群,Azure 文档包含了如何使用这些工具创建您的集群的步骤,网址为docs.microsoft.com/azure/aks

从 Azure 门户开始

我们将使用 Azure 门户开始我们的初始集群部署。Azure 门户是一个基于 Web 的管理控制台。它允许您通过单个控制台构建、管理和监视全球范围内的所有 Azure 部署。

注意

要跟随本书中的示例,您需要一个 Azure 帐户。如果您没有 Azure 帐户,可以按照azure.microsoft.com/free上的步骤创建免费帐户。如果您计划在现有订阅中运行此操作,您将需要订阅的所有者权限以及在 Azure Active Directory(Azure AD)中创建服务主体的能力。

本书中的所有示例都已通过免费试用帐户进行验证。

我们将通过创建 Azure Kubernetes Service(AKS)集群来直接开始。通过这样做,我们还将熟悉 Azure 门户。

创建您的第一个 AKS 集群

在 Azure 门户顶部的搜索栏中输入aks关键字。在结果中,点击服务类别下的Kubernetes 服务

图 2.1:在顶部的搜索栏中搜索 aks,查找服务类别中的 Kubernetes 服务

图 2.1:使用搜索栏在 Azure 门户顶部输入aks关键字

这将带您到门户中的 AKS 刀片。正如您可能期望的那样,您还没有任何集群。继续通过点击+添加按钮创建一个新的集群:

图 2.2:在 AKS 刀片内选择左上角的添加按钮,开始集群创建过程。

图 2.2:单击“+添加”按钮开始集群创建过程

注意

在创建 AKS 集群时有很多配置选项。对于您的第一个集群,我们建议使用门户中的默认设置,并在本示例中遵循我们的命名指南。以下设置经过我们测试,可靠地在免费帐户中使用。

这将带您通过创建向导来创建您的第一个 AKS 集群。这里的第一步是创建一个新的资源组。点击“创建新”按钮,给您的资源命名,并点击“确定”。如果您想跟随本书中的示例,请将资源组命名为rg-handsonaks

显示如何在 AKS 刀片中创建新资源组的过程。第 1 步是选择创建新链接,第 2 步是输入资源组的名称(在本例中为 rg-handsonaks),第 3 步是选择确定。

图 2.3:创建新的资源组

接下来,我们将提供我们的集群详细信息。给您的集群取一个名字 - 如果您想要按照书中的示例进行,请将其命名为handsonaks。我们在书中将使用的区域是(US) West US 2,但您可以选择靠近您位置的任何其他区域。我们将使用 Kubernetes 版本1.15.7,但如果该版本对您不可用,不用担心。Kubernetes 和 AKS 发展非常迅速,经常会推出新版本。接下来,您需要提供一个 DNS 名称前缀。这不必是唯一的,因为 Azure 将在其后附加随机字符:

输入集群详细信息,如 Kubernetes 集群名称、区域、Kubernetes 版本和 DNS 名称前缀。

图 2.4:提供集群详细信息

接下来,我们需要更改节点大小和节点计数。为了优化我们示例集群的免费预算,我们将使用一个没有高级存储的虚拟机VM)并部署一个两节点集群。如果您没有使用免费试用版,您可以选择更强大的 VM 大小,尽管这对本书中的实验并非必需。

点击机器大小下方的更改大小按钮:

使用更改大小选项选择较小的机器。

图 2.5:点击“更改大小”选项以选择较小的机器

删除寻找高级存储的筛选器,寻找D1_v2。然后将节点计数的滑块更改为2

导航到更改大小选项后,删除寻找高级存储的筛选器,并选择 D1_v2 机器类型。

图 2.6:选择 D1_v2 作为机器大小

这应该使您的集群大小看起来类似于图 2.7所示的样子:

使用滑块将节点计数更改为 2 个节点。同时,节点大小已成功更改为 D1 v2。

图 2.7:更新的节点大小和节点计数

注意

您的免费帐户有一个四核限制,如果我们使用默认设置将会违反这一限制。

第一个标签页的最终视图应该类似于图 2.8。我们将不更改我们的演示集群的许多其他配置选项。由于我们已经准备好,现在点击“审阅+创建”按钮进行最终审阅并创建您的集群:

第一个标签页中显示创建集群配置的集群详细信息的基本选项卡。

图 2.8:设置集群配置

在最终视图中,Azure 将验证应用于您的第一个集群的配置。如果验证通过,请点击创建

Azure 门户在您点击左下角的创建按钮之前,提供集群配置的最终验证,并显示所有集群详细信息。

图 2.9:您集群配置的最终验证

部署您的集群大约需要 15 分钟。部署完成后,您可以查看部署详细信息,如图 2.10所示:

微软.aks 概述窗口显示集群成功部署后的部署详细信息。

图 2.10:集群成功部署后的部署详细信息

如果您遇到配额限制错误,如图 2.11所示,请检查设置并重试。确保您选择了D1_v2节点大小,并且只有两个节点:

概述窗口显示错误详细信息和错误摘要。在这里,错误消息显示为:资源操作完成,终端配置状态为“失败”。

图 2.11:由于配额限制错误,重试使用较小的集群大小

要进入下一节,我们将快速查看我们的集群,请点击“转到资源”按钮,这将带您进入门户中的 AKS 标签页。

Azure 门户中您集群的快速概述

如果您在上一节中点击了“转到资源”按钮,现在应该在 Azure 门户中看到您集群的概述:

Azure 门户中 AKS 标签页的概述选项卡显示已部署集群的详细信息。在左侧窗格中,有诸如概述、节点池、升级和洞察等有用的选项卡。

图 2.12:Azure 门户中的 AKS 标签页

这是对您的集群的快速概述。它提供了名称、位置和 API 服务器地址。左侧导航菜单提供了不同的选项来控制和管理您的集群。让我们走一遍门户提供的一些有趣的选项。

第一个有趣的选项是节点池选项。在节点池视图中,您可以通过添加或删除节点来扩展现有的节点池(即集群中的节点或服务器);您可以添加一个新的节点池,可能具有不同的服务器大小,还可以单独升级您的节点池。在图 2.13中,您可以在左上角看到添加节点池选项,并在右侧菜单中看到扩展升级的选项:

节点池刀片显示了如何添加节点池,并提供了一个下拉菜单来扩展和升级现有的节点池。

图 2.13:添加、扩展和升级节点池

第二个有趣的刀片是升级刀片。在这里,您可以指示 AKS 将管理平面升级到更新版本。通常,在 Kubernetes 升级中,您首先升级主平面,然后分别升级各个节点池:

升级刀片有升级 API 服务器的 Kubernetes 版本的选项。

图 2.14:使用升级刀片升级 API 服务器的 Kubernetes 版本

调查的最后一个有趣的地方是InsightsInsights选项为您提供了对集群基础设施和运行在集群上的工作负载的监控。由于我们的集群是全新的,没有太多数据可以调查。我们将在第七章中再次回到这里,监控 AKS 集群和应用程序:

Insights 刀片显示了您的集群利用率。这将在第七章“监控 AKS 集群和应用程序”中进行详细探讨。

图 2.15:使用 Insights 刀片显示集群利用率

这就结束了我们对集群和 Azure 门户中一些有趣的配置选项的快速概述。在下一节中,我们将使用 Cloud Shell 连接到我们的 AKS 集群,然后在我们的集群上启动一个演示应用程序。

使用 Azure Cloud Shell 访问您的集群

一旦部署成功完成,请在搜索栏附近找到小的 Cloud Shell 图标,如图 2.16中所示,并单击它:

通过单击顶部的云外壳图标,在搜索栏旁边启动 Azure 云外壳。

图 2.16:单击云外壳图标以打开 Azure 云外壳

门户网站会要求您选择 PowerShell 或 Bash 作为默认的外壳体验。由于我们将主要使用 Linux 工作负载,请选择Bash

选择 Bash 选项以便按照本书中的示例进行操作。

图 2.17:选择 Bash 选项

如果这是您第一次启动云外壳,系统会要求您创建一个存储帐户;请确认并创建它。

您可能会收到包含挂载存储错误的错误消息。如果出现该错误,请重新启动您的云外壳:

示例显示警告消息,显示“无法挂载 Azure 文件共享。您的云驱动器将不可用”。这可以通过在云外壳顶部选择重新启动按钮来解决。

图 2.18:点击重新启动按钮以解决挂载存储错误

单击电源按钮。它应该重新启动,您应该看到类似图 2.19的东西:

重新选择电源按钮后,云外壳成功启动 Bash。

图 2.19:成功启动云外壳

您可以拉动分隔器/分隔线,以查看更多或更少的外壳内容:

云外壳窗口的分隔器由三个点表示,可以用来使云外壳变大或变小。

图 2.20:使用分隔器使云外壳变大或变小

用于与 Kubernetes 集群进行交互的命令行工具称为kubectl。使用 Azure 云外壳的好处是,这个工具以及许多其他工具都是预安装的,并且定期维护。kubectl使用存储在~/.kube/config中的配置文件来存储访问集群的凭据。

注意

在 Kubernetes 社区中有一些关于正确发音kubectl的讨论。通常的发音方式是 kube-c-t-l、kube-control 或 kube-cuddle。

要获取访问集群所需的凭据,您需要输入以下命令:

az aks get-credentials --resource-group rg-handsonaks --name handsonaks

要验证您是否有访问权限,请输入以下内容:

kubectl get nodes

你应该看到类似图 2.21的东西:

显示节点详细信息,如节点名称、状态、角色、年龄和版本的输出。

图 2.21:kubectl get nodes 命令的输出

这个命令已经验证了我们可以连接到我们的 AKS 集群。在下一节中,我们将继续启动我们的第一个应用程序。

部署您的第一个演示应用程序

您已经连接好了。我们现在将启动我们的第一个应用程序。在 Cloud Shell 中,有两种编辑代码的选项。您可以使用命令行工具,如vinano,也可以通过输入code命令来使用图形代码编辑器。我们将在我们的示例中使用图形编辑器,但请随意使用您感觉最舒适的工具。

为了本书的目的,所有的代码示例都托管在一个 GitHub 存储库中。您可以将此存储库克隆到您的 Cloud Shell,并直接使用代码示例。要将 GitHub 存储库克隆到您的 Cloud Shell,请使用以下命令:

git clone https://github.com/PacktPublishing/Hands-On-Kubernetes-on-Azure---Second-Edition.git Hands-On-Kubernetes-on-Azure

要访问本章的代码示例,请进入代码示例目录并转到Chapter02目录:

cd Hands-On-Kubernetes-on-Azure/Chapter02/

我们现在将直接在那里使用代码。在本书的这一部分,我们暂时不会关注YAML文件中的内容。本章的目标是在集群上启动一个应用程序。在接下来的章节中,我们将深入探讨它们是如何构建的,以及您如何创建自己的应用程序。

我们将根据azure-vote.yaml文件中的定义创建一个应用程序。要在 Cloud Shell 中打开该文件,您可以输入以下命令:

code azure-vote.yaml

以下是您方便的代码示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: azure-vote-back
spec:
  replicas: 1
  selector:
    matchLabels:
      app: azure-vote-back
  template:
    metadata:
      labels:
        app: azure-vote-back
    spec:
      containers:
      - name: azure-vote-back
        image: redis
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 250m
            memory: 256Mi
        ports:
        - containerPort: 6379
          name: redis
---
apiVersion: v1
kind: Service
metadata:
  name: azure-vote-back
spec:
  ports:
  - port: 6379
  selector:
    app: azure-vote-back
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: azure-vote-front
spec:
  replicas: 1
  selector:
    matchLabels:
      app: azure-vote-front
  template:
    metadata:
      labels:
        app: azure-vote-front
    spec:
      containers:
      - name: azure-vote-front
        image: microsoft/azure-vote-front:v1
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 250m
            memory: 256Mi
        ports:
        - containerPort: 80
        env:
        - name: REDIS
          value: "azure-vote-back"
---
apiVersion: v1
kind: Service
metadata:
  name: azure-vote-front
spec:
  type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: azure-vote-front

您可以在 Cloud Shell 代码编辑器中对文件进行更改。如果您进行了更改,可以点击右上角的...图标,然后点击保存来保存文件:

浏览器中的代码编辑器。点击三个点保存文件。

图 2.22:点击[...]保存文件

文件应该已保存。您可以使用以下命令检查:

cat azure-vote.yaml

注意:

在 Linux 中,按下Tab按钮会展开文件名。在前面的场景中,如果您在输入az后按下Tab,它应该会展开为azure-vote.yaml

现在,让我们启动应用程序:

kubectl create -f azure-vote.yaml

您应该很快就会看到图 2.23中显示的输出,告诉您已创建了哪些资源:

kubectl create 命令的输出,显示已创建了 azure-vote-back 和 azure-vote-front 部署以及 azure-vote-back 和 azure-vote-front 服务。

图 2.23:kubectl create 命令的输出

您可以通过输入以下内容来检查进度:

kubectl get pods

如果您输入得很快,您可能会看到某个 pod 仍处于ContainerCreating过程中:

使用 kubectl get pods -w 命令检查集群中 pod 的进度。

图 2.24:kubectl get pods 命令的输出

注意

输入kubectl可能会变得乏味。您可以使用alias命令使生活变得更轻松。您可以使用以下命令将k作为kubectl的别名:alias k=kubectl。运行上述命令后,您只需使用k get pods。出于本书的教学目的,我们将继续使用完整的kubectl命令。

按上箭头键并按Enter,直到所有 pod 的状态都为Running。设置所有 pod 需要一些时间,您可以使用以下命令跟踪它们的状态:

kubectl get pods --watch

要停止跟踪 pod 的状态(当它们全部处于运行状态时),可以按Ctrl + C(Mac 上为command + C)。

为了公开访问我们的应用程序,我们需要等待另一件事。现在我们想知道负载均衡器的公共 IP,以便我们可以访问它。如果您还记得第一章,Docker 和 Kubernetes 简介中的内容,Kubernetes 中的服务将创建一个 Azure 负载均衡器。这个负载均衡器将在我们的应用程序中获得一个公共 IP,以便我们可以公开访问它。

键入以下命令以获取负载均衡器的公共 IP:

kubectl get service azure-vote-front --watch

起初,外部 IP 可能显示为“待定”。等待公共 IP 出现,然后按Ctrl + C(Mac 上为command + C)退出:

kubectl get service --watch 的输出。您可以看到服务 IP 从待定更改为实际 IP 地址。

图 2.25:从待定 IP 更改为实际 IP 地址的服务 IP

注意外部 IP 地址,并在浏览器中输入。您应该看到图 2.26中显示的输出:

启动应用程序,其中包含三个按钮(猫、狗和重置),以及底部的猫和狗的计数。单击猫和狗,计数会增加,重置可以用于将计数设置为零。

图 2.26:您刚刚启动的实际应用程序

单击,观察计数增加。

您现在已经启动了自己的集群和第一个 Kubernetes 应用程序。请注意,Kubernetes 负责诸如连接前端和后端、将其暴露给外部世界以及为服务提供存储等任务。

在进入下一章之前,我们将清理我们的部署。由于我们是从一个文件创建的所有内容,我们也可以通过将 Kubernetes 指向该文件来删除所有内容。键入kubectl delete -f azure-vote.yaml,观察所有对象被删除:

kubectl delete -f azure-vote.yaml 命令的输出,验证部署和服务已被删除。

图 2.27:清理部署

在本节中,我们已经使用 Cloud Shell 连接到了我们的 AKS 集群,成功启动并连接到了演示应用程序,最后清理了创建的资源。

摘要

完成了本章后,您可以访问并浏览 Azure 门户,执行部署 AKS 集群所需的所有功能。您利用 Azure 的免费试用来学习 AKS 和其他 Azure 服务的方方面面。您启动了自己的 AKS 集群,并可以使用 Azure 门户根据需要自定义配置。

您还可以在不在计算机上安装任何内容的情况下使用 Azure Cloud Shell。这对于接下来的所有部分都很重要,在那里您将做的不仅仅是启动简单的应用程序。最后,您启动了一个可公开访问的服务。这个应用程序的框架与您将在后面的章节中启动的复杂应用程序相同。

在下一章中,我们将深入研究不同的部署选项,将应用程序部署到 AKS 上。

第二部分:在 AKS 上部署

在本书的这一部分中,我们已经介绍了 Docker 和 Kubernetes 的基础知识,并在 Azure 上设置了一个 Kubernetes 集群。在本节中,我们将介绍如何在该 Kubernetes 集群上部署应用程序。

在本节中,我们将逐步构建和部署不同的应用程序在 AKS 之上。我们将从部署一个简单的应用程序开始,然后介绍诸如扩展、监控和认证等概念。到本节结束时,您应该能够轻松地将应用程序部署到 AKS 上。

本节包含以下章节:

  • 第三章,在 AKS 上部署应用程序

  • 第四章,构建可扩展的应用程序

  • 第五章,在 AKS 中处理常见故障

  • 第六章,使用 HTTPS 和 Azure AD 保护您的应用程序

  • 第七章,监控 AKS 集群和应用程序

第三章:在 AKS 上部署应用程序

在本章中,我们将在Azure Kubernetes ServiceAKS)上部署两个应用程序。一个应用程序由多个部分组成,您将一步一步地构建这些应用程序,同时解释它们背后的概念模型。您将能够轻松地将本章中的步骤调整为在 AKS 上部署任何其他应用程序。

部署应用程序并对其进行更改时,您将使用 YAML 文件。YAML 是YAML Ain't Markup Language的首字母缩略词。YAML 是一种用于创建配置文件以部署到 Kubernetes 的语言。虽然您可以使用 JSON 或 YAML 文件来部署应用程序到 Kubernetes,但 YAML 是最常用的语言。YAML 变得流行是因为与 JSON 或 XML 相比,人类更容易阅读它。在本章和整本书中,您将看到多个 YAML 文件的示例。

在部署示例 guestbook 应用程序时,您将看到 Kubernetes 概念的实际应用。您将看到部署ReplicaSet的关联,以及它与部署的Pods的关联。部署是 Kubernetes 中用于定义应用程序期望状态的对象。部署将创建一个 ReplicaSet。ReplicaSet 是 Kubernetes 中保证一定数量的 Pod 始终可用的对象。因此,ReplicaSet 将创建一个或多个 Pods。Pod 是 Kubernetes 中的一个对象,它是一个或多个容器的组。让我们重新审视部署、ReplicaSet 和 Pod 之间的关系:

该图描述了部署、ReplicaSet 和 Pod 之间的关联。

图 3.1:部署、ReplicaSet 和 Pod 之间的关系

在部署示例应用程序时,您将使用服务对象来连接应用程序。Kubernetes 中的服务是用于为应用程序提供静态 IP 地址和 DNS 名称的对象。由于 Pod 可以在集群中被杀死并移动到不同的节点,服务确保您可以连接到应用程序的静态终点。

您还将编辑示例应用程序,使用 ConfigMap 提供配置细节。ConfigMap 是一个用于向 Pod 提供配置细节的对象。它允许您将配置设置保留在容器之外。然后,您可以通过将 ConfigMap 连接到部署来向应用程序提供这些配置细节。

最后,您将了解 Helm。Helm 是 Kubernetes 的包管理器,有助于简化部署过程。您将使用 Helm 部署 WordPress 网站,并了解 Helm 为 Kubernetes 带来的价值。WordPress 安装在 Kubernetes 中使用持久存储。您将学习如何设置 AKS 中的持久存储。

本章将涵盖以下主题:

  • 部署示例留言板应用程序

  • 完整部署示例留言板应用程序

  • 使用 Helm 安装复杂的 Kubernetes 应用程序

我们将从示例留言板应用程序开始。

部署示例留言板应用程序

在本章中,您将部署经典的留言板示例 Kubernetes 应用程序。您将主要按照Kubernetes.io/docs/tutorials/stateless-application/guestbook/中的步骤进行,但会进行一些修改。您将使用这些修改来展示额外的概念,比如 ConfigMaps,这些概念在原始示例中并不存在。

示例留言板应用程序是一个简单的多层 Web 应用程序。该应用程序中的不同层将具有多个实例。这对于高可用性和扩展都是有益的。留言板的前端是一个无状态应用程序,因为前端不存储任何状态。后端的 Redis 集群是有状态的,因为它存储所有留言板条目。

你将在下一章中使用这个应用程序作为测试后端和前端独立扩展的基础。

在开始之前,让我们考虑一下我们将要部署的应用程序。

介绍应用程序

该应用程序存储并显示留言板条目。您可以使用它来记录所有访问您的酒店或餐厅的人的意见。在此过程中,我们将解释 Kubernetes 概念,如部署和 ReplicaSets。

该应用程序使用 PHP 作为前端。前端将使用多个副本进行部署。该应用程序使用 Redis 作为数据存储。Redis 是一种内存中的键值数据库。Redis 通常用作缓存。根据www.datadoghq.com/docker-adoption/,它是最受欢迎的容器映像之一。

guestbook 应用的概述。用户连接到前端。前端然后连接到 Redis 主节点或 Redis 从节点之一。

图 3.2:guestbook 应用的高级概述

我们将通过部署 Redis 主节点来开始部署此应用程序。

部署 Redis 主节点

在本节中,您将部署 Redis 主节点。您将了解到此部署所需的 YAML 语法。在下一节中,您将对此 YAML 进行更改。在进行更改之前,让我们先部署 Redis 主节点。

执行以下步骤完成任务:

  1. 打开友好的 Cloud Shell,如图 3.3中所示:要打开 Cloud Shell,请单击搜索栏右侧的图标。
图 3.3:打开 Cloud Shell
  1. 如果您尚未克隆此书的 github 存储库,请使用以下命令进行克隆:
git clone https://github.com/PacktPublishing/Hands-On-Kubernetes-on-Azure---Second-Edition Hands-On-Kubernetes-on-Azure
cd Hands-On-Kubernetes-on-Azure/Chapter03/
  1. 输入以下命令以部署主节点:
kubectl apply -f redis-master-deployment.yaml

应用程序下载和启动需要一些时间。在等待时,让我们了解您刚刚输入和执行的命令。让我们开始探索使用的 YAML 文件的内容:

1 apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
2 kind: Deployment
3 metadata:
4   name: redis-master
5   labels:
6     app: redis
7 spec:
8   selector:
9     matchLabels:
10      app: redis
11      role: master
12      tier: backend
13  replicas: 1
14  template:
15    metadata:
16      labels:
17        app: redis
18        role: master
19        tier: backend
20    spec:
21      containers:
22      - name: master
23        image: k8s.gcr.io/redis:e2e # or just image: redis
24        resources:
25          requests:
26            cpu: 100m
27            memory: 100Mi
28        ports:
29        - containerPort: 6379

让我们深入了解提供的参数的代码:

  • 第 2 行:这说明我们正在创建一个“部署”。如《第一章》Docker 和 Kubernetes 简介中所解释的,部署是围绕 Pods 的包装,使得更新和扩展 Pods 变得容易。

  • 第 4-6 行:在这里,“部署”被赋予一个名称,即redis-master

  • 第 7-12 行:这些行让我们指定此“部署”将管理的容器。在此示例中,“部署”将选择和管理所有标签匹配的容器(app: redisrole: mastertier: backend)。前面的标签与第 14-19 行提供的标签完全匹配。

  • 第 13 行:告诉 Kubernetes 我们需要确切地运行一个 Redis 主节点的副本。这是 Kubernetes 声明性质的一个关键方面。您提供了应用程序需要运行的容器的描述(在本例中,只有一个 Redis 主节点的副本),Kubernetes 会处理它。

  • 第 14-19 行:为运行的实例添加标签,以便将其分组并连接到其他容器。我们将在后面讨论它们是如何使用的。

  • 第 22 行:为这个容器指定一个名字,即master。在多容器 Pod 的情况下,Pod 中的每个容器都需要一个唯一的名字。

  • 第 23 行:这一行指示将要运行的 Docker 镜像。在这种情况下,它是使用e2e标记的redis镜像(最新通过端到端[e2e]测试的 Redis 镜像)。

  • 第 28-29 行:这两行表示容器将监听6379端口。

  • 第 24-27 行:设置容器请求的cpu/memory资源。在这种情况下,请求的是 0.1 CPU,即100m,通常也被称为 100 毫核。请求的内存是100Mi,或 104857600 字节,大约等于 105MB (Kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/)。您也可以以类似的方式设置 CPU 和内存限制。限制是容器可以使用的上限。如果您的 Pod 达到了 CPU 限制,它将被限制,而如果它达到了内存限制,它将被重新启动。在 Kubernetes 中设置请求和限制是最佳实践。

注意

Kubernetes 的 YAML 定义类似于 Docker 运行特定容器镜像时给出的参数。如果您必须手动运行这个例子,您可以这样定义:

# 运行一个名为 master 的容器,监听 6379 端口,使用 redis:e2e 镜像,内存为 100M,CPU 为 100m。

docker run --name master -p 6379:6379 -m 100M -c 100m -d k8s.gcr.io/redis:e2e

在这一部分,您已经部署了 Redis 主节点,并学习了用于创建此部署的 YAML 文件的语法。在下一部分,您将检查部署并了解创建的不同元素。

检查部署

redis-master部署现在应该已经完成。继续在您在上一部分打开的 Azure Cloud Shell 中输入以下内容:

kubectl get all

你应该会得到图 3.4中显示的输出:

使用 kubectl get all 命令,您将看到创建了诸如 Pod、service、Deployment 和 ReplicaSet 等对象。

图 3.4:显示部署创建的对象的输出

你可以看到我们有一个名为redis-master的部署。它控制着一个redis-master-<随机 id>的 ReplicaSet。进一步检查后,你还会发现 ReplicaSet 正在控制一个名为redis-master-<replica set 随机 id>-<随机 id>的 Pod。图 3.1以图形方式展示了这种关系。

通过执行kubectl describe <object> <instance-name>命令,可以获得更多详细信息,如下所示:

kubectl describe deployment/redis-master

这将生成以下输出:

通过使用 kubectl describe deployment/redis-master 命令,您将看到部署的更多细节。

图 3.5:描述部署的输出

您现在已经启动了一个具有默认配置的 Redis 主节点。通常,您会使用特定于环境的配置启动应用程序。

在下一节中,我们将介绍一个名为 ConfigMaps 的新概念,然后重新创建 Redis 主节点。因此,在继续之前,我们需要清理当前版本,可以通过运行以下命令来完成:

kubectl delete deployment/redis-master

执行此命令将产生以下输出:

deployment.extensions "redis-master" deleted

在本节中,您检查了您创建的 Redis 主节点部署。您看到了部署与 ReplicaSet 的关系,以及 ReplicaSet 与 Pod 的关系。在接下来的部分中,您将使用通过 ConfigMap 提供的特定于环境的配置重新创建这个 Redis 主节点。

带有 ConfigMap 的 Redis 主节点

以前的部署没有任何问题。在实际使用情况中,很少会启动一个没有一些配置设置的应用程序。在这种情况下,我们将使用 ConfigMap 为redis-master设置配置设置。

ConfigMap 是一种便携的配置容器的方式,而不需要为每个配置专门的镜像。它具有需要在容器上设置的数据的键值对。ConfigMap 用于非机密配置。Kubernetes 有一个名为Secret的单独对象。Secret 用于包含关键数据的配置,如密码。这将在本书的第十章保护您的 AKS 集群中详细探讨。

在此示例中,我们将创建一个 ConfigMap。在此 ConfigMap 中,我们将redis-config配置为键,值将为:

maxmemory 2mb

maxmemory-policy allkeys-lru

现在,让我们创建此 ConfigMap。有两种创建 ConfigMap 的方法:

  • 从文件创建 ConfigMap

  • 从 YAML 文件创建 ConfigMap

我们将详细探讨每一个。

从文件创建 ConfigMap

以下步骤将帮助我们从文件创建 ConfigMap:

  1. 通过在终端中键入code redis-config来打开 Azure Cloud Shell 代码编辑器。复制并粘贴以下两行,并将其保存为redis-config
maxmemory 2mb
maxmemory-policy allkeys-lru
  1. 现在您可以使用以下代码创建 ConfigMap:
kubectl create configmap example-redis-config --from-file=redis-config
  1. 您应该得到以下输出:
configmap/example-redis-config created
  1. 您可以使用相同的命令描述此 ConfigMap:
kubectl describe configmap/example-redis-config
  1. 输出将如图 3.6所示:使用 kubectl describe configmap/example-redis-config 命令,将生成提供名称、命名空间、标签、注释、数据、redis-config、内存和事件等详细信息的输出。
图 3.6:描述 ConfigMap 的输出

在此示例中,您通过引用磁盘上的文件创建了 ConfigMap。部署 ConfigMaps 的另一种方法是通过从 YAML 文件创建它们。让我们看看在以下部分如何完成这一点。

从 YAML 文件创建 ConfigMap

在本节中,您将使用 YAML 文件从上一节重新创建 ConfigMap:

  1. 首先,删除先前创建的 ConfigMap:
kubectl delete configmap/example-redis-config
  1. 将以下行复制并粘贴到名为example-redis-config.yaml的文件中,然后保存该文件:
apiVersion: v1
data:
  redis-config: |- 
    maxmemory 2mb
    maxmemory-policy allkeys-lru
kind: ConfigMap
metadata:
  name: example-redis-config
  namespace: default
  1. 现在,您可以通过以下命令重新创建您的 ConfigMap:
kubectl create -f example-redis-config.yaml
  1. 您应该得到以下输出:
configmap/example-redis-config created
  1. 接下来,运行以下命令:
kubectl describe configmap/example-redis-config
  1. 此命令返回与先前相同的输出:
Name:           example-redis-config
Namespace:      default
Labels:         <none>
Annotations:    <none>
Data
====
redis-config:
----
maxmemory 2mb
maxmemory-policy allkeys-lru
Events:	<none>

如您所见,使用 YAML 文件,您能够创建相同的 ConfigMap。

注意:

kubectl get具有有用的选项-o,可用于以 YAML 或 JSON 格式获取对象的输出。这在您对系统进行手动更改并希望以 YAML 格式查看生成的对象时非常有用。您可以使用以下命令以 YAML 格式获取当前的 ConfigMap:

kubectl get -o yaml configmap/example-redis-config

现在您已经定义了 ConfigMap,让我们使用它。

使用 ConfigMap 读取配置数据

在本节中,您将重新配置redis-master部署以从 ConfgMap 读取配置:

  1. 首先,修改redis-master-deployment.yaml以使用 ConfigMap 如下。在源代码之后将解释需要进行的更改:

注意

如果您下载了本书附带的源代码,在第三章在 AKS 上部署应用程序中有一个名为redis-master-deployment_Modified.yaml的文件,其中已经应用了必要的更改。

1 apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
2  kind: Deployment
3  metadata:
4    name: redis-master
5    labels:
6      app: redis
7  spec:
8    selector:
9      matchLabels:
10       app: redis
11       role: master
12       tier: backend
13   replicas: 1
14   template:
15     metadata:
16       labels:
17         app: redis
18         role: master
19         tier: backend
20     spec:
21       containers:
22       - name: master
23         image: k8s.gcr.io/redis:e2e
24         command:
25         - redis-server
26         - "/redis-master/redis.conf"
27         env:
28         - name: MASTER
29           value: "true"
30         volumeMounts:
31         - mountPath: /redis-master
32           name: config
33         resources:
34           requests:
35             cpu: 100m
36             memory: 100Mi
37         ports:
38         - containerPort: 6379
39       volumes:
40         - name: config
41           configMap:
42             name: example-redis-config
43             items:
44             - key: redis-config
45               path: redis.conf

让我们深入了解代码,以理解不同的部分:

  • 第 24-26 行:这些行介绍了在 Pod 启动时将执行的命令。在这种情况下,这将启动指向特定配置文件的redis-server

  • 第 27-29 行:显示如何将配置数据传递给正在运行的容器。这种方法使用环境变量。在 Docker 形式中,这相当于docker run -e "MASTER=true". --name master -p 6379:6379 -m 100M -c 100m -d Kubernetes /redis:v1。这将环境变量MASTER设置为true。您的应用程序可以读取环境变量设置以进行配置。

  • 第 30-32 行:这些行在运行的容器上的/redis-master路径上挂载名为config的卷(此卷在第 39-45 行中定义)。它将隐藏原始容器上/redis-master上存在的任何内容。

以 Docker 术语来说,相当于docker run -v config:/redis-master. -e "MASTER=TRUE" --name master -p 6379:6379 -m 100M -c 100m -d Kubernetes /redis:v1

  • 第 40 行:为卷命名为config。此名称将在此 Pod 的上下文中使用。

  • 第 41-42 行:声明应该从example-redis-config ConfigMap 加载此卷。此 ConfigMap 应该已经存在于系统中。您已经定义了这一点,所以没问题。

  • 第 43-45 行:在这里,您正在将redis-config键的值(两行maxmemory设置)加载为redis.conf文件。

  1. 让我们创建这个更新后的部署:
kubectl create -f redis-master-deployment_Modified.yml
  1. 这应该输出以下内容:
deployment.apps/redis-master created
  1. 现在让我们确保配置已成功应用。首先,获取 Pod 的名称:
kubectl get pods
  1. 然后exec进入 Pod 并验证已应用设置:
kubectl exec -it redis-master-<pod-id> redis-cli
127.0.0.1:6379&gt; CONFIG GET maxmemory
  1) "maxmemory" 2) "2097152"
127.0.0.1:6379&gt; CONFIG GET maxmemory-policy
  "maxmemory-policy"
  "allkeys-lru" 127.0.0.1:6379&gt;exit

总之,您刚刚执行了配置云原生应用程序的重要且棘手的部分。您还会注意到应用程序必须配置为动态读取配置。配置应用程序后,您访问了正在运行的容器以验证运行配置。

注意

连接到运行中的容器对于故障排除和诊断非常有用。由于容器的短暂性质,您不应该连接到容器进行额外的配置或安装。这应该是您容器镜像的一部分,或者是您通过 Kubernetes 提供的配置(就像我们刚刚做的那样)。

在本节中,您配置了 Redis Master 从 ConfigMap 加载配置数据。在下一节中,我们将部署端到端的应用程序。

示例 guestbook 应用程序的完整部署

在了解使用 ConfigMap 动态配置应用程序的过程中,我们将现在返回到部署其余 guestbook 应用程序的过程中。您将再次遇到部署、ReplicaSets 和后端和前端的 Pods 的概念。除此之外,您还将被介绍另一个关键概念,称为服务。

为了开始完整的部署,我们将创建一个服务来公开 Redis 主服务。

公开 Redis 主服务

在普通 Docker 中公开端口时,公开的端口受限于其运行的主机。在 Kubernetes 网络中,集群中不同 Pod 之间存在网络连接。但是,Pod 本身是短暂的,这意味着它们可以被关闭、重新启动,甚至移动到其他主机而不保留其 IP 地址。如果您直接连接到 Pod 的 IP,如果该 Pod 被移动到新主机,您可能会失去连接。

Kubernetes 提供了service对象,它处理了这个确切的问题。使用标签匹配选择器,它代理流量到正确的 Pod,并进行负载平衡。在这种情况下,主服务只有一个 Pod,因此它只确保流量被定向到独立于 Pod 所在节点的 Pod。要创建服务,请运行以下命令:

kubectl apply -f redis-master-service.yaml 

Redis 主服务具有以下内容:

1   apiVersion: v1
2   kind: Service
3   metadata:
4     name: redis-master
5     labels:
6       app: redis
7       role: master
8       tier: backend
9   spec:
10   ports:
11   - port: 6379
12     targetPort: 6379
13    selector:
14      app: redis
15      role: master
16      tier: backend

现在让我们看看您使用前面的代码创建了什么:

  • 第 1-8 行:这些行告诉 Kubernetes,我们想要一个名为redis-master的服务,它具有与我们的redis-master服务器 Pod 相同的标签。

  • 第 10-12 行:这些行表示服务应该处理到达端口6379的流量,并将其转发到与第 13 行和第 16 行之间定义的选择器匹配的 Pod 的端口6379

  • 第 13-16 行:这些行用于查找需要代理传入流量的 Pod。因此,任何具有匹配标签(app: redisrole: mastertier: backend)的 Pod 都应该处理端口6379的流量。如果您回顾前面的示例,这些标签正是我们应用于该部署的标签。

我们可以通过运行以下命令来检查服务的属性:

kubectl get service

这将给您一个如图 3.7所示的输出:

使用 kubectl get service 命令,您将看到每个服务的名称、类型、集群 IP、外部 IP、端口和年龄等详细信息。

图 3.7:创建的服务的输出

您会看到一个名为redis-master的新服务已创建。它具有集群范围的 IP10.0.227.250(在您的情况下,IP 可能会有所不同)。请注意,此 IP 仅在集群内部有效(因此是ClusterIP类型)。

服务还为该服务引入了一个域名服务器(DNS)名称。DNS 名称的格式为<service-name>.<namespace>.svc.cluster.local;在我们的情况下,那将是redis-master.default.svc.cluster.local。为了看到这一点,我们将在我们的redis-master VM 上进行名称解析。默认镜像没有安装nslookup,所以我们将通过运行ping命令来绕过。如果该流量没有返回,不要担心;这是因为您没有在服务上公开ping,只有redis端口。让我们来看一下:

kubectl get pods
#note the name of your redis-master pod
kubectl exec -it redis-master-<pod-id> bash
ping redis-master

这应该输出结果名称解析,显示您服务的完全合格域名(FQDN)和之前显示的 IP 地址。您可以通过exit命令退出 Pod,如图 3.8所示:

输出显示了服务的 FQDN 以及 IP 地址。

图 3.8:使用 ping 命令查看服务的 FQDN

在这一部分,您使用服务公开了 Redis 主服务器。在下一部分,您将部署 Redis 从服务器。

部署 Redis 从服务器

在云上运行单个后端是不推荐的。您可以在主从设置中配置 Redis。这意味着您可以有一个主服务器用于写入流量,以及多个从服务器用于处理读取流量。这对于处理增加的读取流量和提高可用性非常有用。

让我们来设置一下:

  1. 通过运行以下命令创建部署:
kubectl apply -f redis-slave-deployment.yaml
  1. 让我们现在检查所有已创建的资源:
kubectl get all

输出将如图 3.9所示:

使用 kubectl get all 命令,您会看到新的对象,如 Pod、Deployment 和 ReplicaSet。

图 3.9:部署 Redis 从节点会创建一些新对象
  1. 根据前面的输出,您可以看到您创建了两个redis-slave Pod 的副本。这可以通过检查redis-slave-deployment.yaml文件来确认:
1   apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
2   kind: Deployment
3   metadata:
4     name: redis-slave
5     labels:
6       app: redis
7   spec:
8     selector:
9       matchLabels:
10       app: redis
11       role: slave
12       tier: backend
13   replicas: 2
14   template:
15     metadata:
16       labels:
17         app: redis
18         role: slave
19         tier: backend
20     spec:
21       containers:
22       - name: slave
23         image: gcr.io/google_samples/gb-redisslave:v1
24         resources:
25           requests:
26             cpu: 100m
27             memory: 100Mi
28         env:
29         - name: GET_HOSTS_FROM
30           value: dns
31           # Using 'GET_HOSTS_FROM=dns' requires your cluster to
32           # provide a dns service. As of Kubernetes 1.3, DNS is a built-in
33           # service launched automatically. However, if the cluster you are using
34           # does not have a built-in DNS service, you can instead
35           # access an environment variable to find the master
36           # service's host. To do so, comment out the 'value: dns' line above, and
37           # uncomment the line below:
38           # value: env
39         ports:
40         - containerPort: 6379

除了以下内容之外,其他都是一样的:

  • 第 13 行:副本数量为2

  • 第 23 行:您现在正在使用特定的从节点镜像。

  • 第 29-30 行:将GET_HOSTS_FROM设置为dns。正如您在前面的示例中看到的,DNS 在集群中解析。

  1. 与主服务一样,您需要通过运行以下命令公开从服务:
kubectl apply -f redis-slave-service.yaml

这个服务和redis-master服务之间唯一的区别是,这个服务会将流量代理到具有role:slave标签的 Pod。

  1. 通过运行以下命令来检查redis-slave服务:
kubectl get service

这应该会给您显示图 3.10中显示的输出:

当您执行 kubectl get service 命令时,输出屏幕将显示一个 redis-master 和一个 redis-slave。

图 3.10:输出显示了 redis-master 和 redis-slave 服务

现在您已经拥有一个 Redis 集群正在运行,有一个主节点和两个副本。在下一节中,您将部署和公开前端。

部署和公开前端

到目前为止,您已经专注于 Redis 后端。现在您已经准备好部署前端。这将为您的应用程序添加一个图形网页,您将能够与之交互。

您可以使用以下命令创建前端:

kubectl apply -f frontend-deployment.yaml

要验证部署,请运行此代码:

kubectl get pods

这将显示图 3.11中显示的输出:

当您执行 kubectl get pods 命令时,输出屏幕会显示总共 6 个处于运行状态的 Pod。

图 3.11:显示运行前端的额外 Pod 的输出

您会注意到这个部署指定了3个副本。部署具有通常的方面,但有一些小的变化,如下面的代码所示:

1   apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment
2   metadata:
3     name: frontend
4     labels:
5       app: guestbook
6   spec:
7     selector:
8       matchLabels:
9         app: guestbook
10        tier: frontend
11    replicas: 3
12    template:
13      metadata:
14        labels:
15          app: guestbook
16          tier: frontend
17      spec:
18        containers:
19        - name: php-redis
20          image: gcr.io/google-samples/gb-frontend:v4
21          resources:
22            requests:
23              cpu: 100m
24              memory: 100Mi
25          env:
26          - name: GET_HOSTS_FROM
27            value: dns
28            # Using GET_HOSTS_FROM=dns requires your cluster to
29            # provide a dns service. As of Kubernetes 1.3, DNS is a built-in
30            # service launched automatically. However, if the cluster you are using
31            # does not have a built-in DNS service, you can instead
32            # access an environment variable to find the master
33            # service's host. To do so, comment out the 'value: dns' line above, and
34            # uncomment the line below:
35            # value: env
36          ports:
37          - containerPort: 80

让我们看看这些变化:

  • 第 11 行:副本数量设置为3

  • 第 8-10 行和 14-16 行:标签设置为app: guestbooktier: frontend

  • 第 20 行:使用gb-frontend:v4作为镜像。

您现在已经创建了前端部署。现在您需要将其公开为服务。

公开前端服务

定义 Kubernetes 服务的多种方式。我们创建的两个 Redis 服务都是ClusterIP类型。这意味着它们仅在集群内可访问,如图 3.12所示:

ClusterIP 类型的服务是跨整个集群的 IP,并连接到每个节点上的 pod。ClusterIP 仅在集群内可用。

图 3.12:类型为 ClusterIP 的 Kubernetes 服务

另一种服务类型是NodePort类型。这种服务将在每个节点上的静态端口上暴露,如图 3.13所示:

NodePort 类型的服务将在每个节点上打开一个端口,该端口将连接到每个节点上的 Pod。外部用户可以使用该端口从集群外部连接到 Pod。

图 3.13:类型为 NodePort 的 Kubernetes 服务

最后一种类型 - 我们将在示例中使用的类型 - 是LoadBalancer类型。这将创建一个Azure 负载均衡器,我们可以使用它来连接的公共 IP,如图 3.14所示:

LoadBalancer 类型的服务将创建一个外部负载均衡器,该负载均衡器将连接到每个节点上的 Pod。外部用户可以使用该负载均衡器从集群外部连接到 Pod。

图 3.14:类型为 LoadBalancer 的 Kubernetes 服务

以下代码将帮助我们了解如何暴露前端服务:

1   apiVersion: v1
2   kind: Service
3   metadata:
4     name: frontend
5     labels:
6       app: guestbook
7       tier: frontend
8   spec:
9     # comment or delete the following line if you want to use a LoadBalancer
10    # type: NodePort # line commented out
11    # if your cluster supports it, uncomment the following to automatically create
12    # an external load-balanced IP for the frontend service.
13    type: LoadBalancer # line uncommented
14    ports:
15    - port: 80
16    selector:
17      app: guestbook
18      tier: frontend
  • 现在您已经看到了前端服务是如何暴露的,让我们通过以下步骤使 guestbook 应用程序准备好使用:
  1. 要创建服务,请运行以下命令:
kubectl create -f frontend-service.yaml

当您首次运行此步骤时,执行此步骤需要一些时间。在后台,Azure 必须执行一些操作以使其无缝。它必须创建一个 Azure 负载均衡器和一个公共 IP,并设置端口转发规则,以将端口80上的流量转发到集群的内部端口。

  1. 运行以下命令,直到EXTERNAL-IP列中有值为止:
kubectl get service

这应该显示图 3.15中显示的输出:

执行 kubectl get service 命令时,会生成一个输出,其中前端、kubernetes、redis-master 和 redis-slave 都有一个 Cluster IP。但是,这里只有前端有一个 External IP。

图 3.15:一段时间后显示外部 IP 值的输出
  1. 在 Azure 门户中,如果您点击所有资源并过滤负载均衡器,您将看到一个kubernetes 负载均衡器。点击它会显示类似于图 3.16的内容。突出显示的部分显示了在端口80上接受流量的负载均衡规则,以及您有 2 个公共 IP 地址:在 Azure 门户中打开 Kubernetes 负载均衡器,您将看到一个负载均衡规则和两个出现在屏幕右侧的公共 IP 地址。
图 3.16:在 Azure 门户中显示 Kubernetes 负载均衡器

如果您点击两个公共 IP 地址,您会看到这两个 IP 地址都链接到您的集群。其中一个将是您实际服务的 IP 地址;另一个是 AKS 用于进行出站连接的 IP 地址。

注意:

Azure 有两种类型的负载均衡器:基本和标准。

基本负载均衡器后面的虚拟机可以在没有特定配置的情况下进行出站连接。标准负载均衡器后面的虚拟机(这是 AKS 的默认设置)需要特定配置才能进行出站连接。这就是为什么您会看到第二个 IP 地址配置的原因。

我们终于准备好让我们的 guestbook 应用程序投入使用!

正在运行的 guestbook 应用程序

在您喜欢的浏览器中输入服务的公共 IP。您应该会得到图 3.17中显示的输出:

当您在浏览器中输入 IP 地址时,您将看到一个显示单词 Guestbook 的白屏。这表明 Guestbook 应用程序正在运行。

图 3.17:Guestbook 应用程序正在运行

继续记录您的消息。它们将被保存。打开另一个浏览器并输入相同的 IP;您将看到您输入的所有消息。

恭喜 - 您已经完成了您的第一个完全部署的、多层、云原生的 Kubernetes 应用程序!

为了节省您的免费试用虚拟机资源,最好删除创建的部署,然后使用以下命令运行下一轮部署:

kubectl delete deployment frontend redis-master redis-slave
kubectl delete service frontend redis-master redis-slave

在前面的章节中,您已经部署了一个 Redis 集群并部署了一个可公开访问的 Web 应用程序。您已经了解了部署、ReplicaSets 和 Pods 之间的关联,以及 Kubernetes 如何使用service对象来路由网络流量。在本章的下一节中,您将使用 Helm 在 Kubernetes 上部署一个更复杂的应用程序。

使用 Helm 安装复杂的 Kubernetes 应用程序

在上一节中,我们使用静态 YAML 文件部署了我们的应用程序。当部署更复杂的应用程序跨多个环境(如开发/测试/生产)时,手动编辑每个环境的 YAML 文件可能会变得繁琐。这就是 Helm 工具的用武之地。

Helm 是 Kubernetes 的包管理器。Helm 帮助您以规模部署、更新和管理 Kubernetes 应用程序。为此,您需要编写一种称为 Helm Charts 的东西。

您可以将 Helm Charts 视为参数化的 Kubernetes YAML 文件。如果您考虑一下我们在上一节中编写的 Kubernetes YAML 文件,那些文件是静态的。您需要进入文件并编辑它们以进行更改。

Helm Charts 允许您编写带有特定参数的 YAML 文件,您可以动态设置这些参数。可以通过值文件或在部署图表时作为命令行变量来设置这些参数。

最后,使用 Helm,您不一定需要自己编写 Helm Charts;您还可以使用丰富的预先编写的 Helm Charts 库,并通过简单的命令(例如helm install --name my-release stable/mysql)在集群中安装流行的软件。

这正是您将在下一节中要做的。您将通过发出仅两个命令在您的集群上安装 WordPress。在接下来的章节中,您还将深入研究自定义 Helm Charts。

注意

2019 年 11 月 13 日,Helm v3 的第一个稳定版本发布。在接下来的示例中,我们将使用 Helm v3。Helm v2 和 Helm v3 之间最大的区别是,Helm v3 是一个完全客户端工具,不再需要名为tiller的服务器端工具。

如果您想更全面地了解如何编写自己的 Helm Charts,可以参考本书作者之一的博客文章:blog.nillsf.com/index.php/2019/11/23/writing-a-helm-v3-chart/

让我们从使用 Helm 在您的集群上安装 WordPress 开始。在本节中,您还将了解 Kubernetes 中的持久存储。

使用 Helm 安装 WordPress

如介绍中所述,Helm 具有丰富的预先编写的 Helm Charts 库。要访问此库,您需要向 Helm 客户端添加一个存储库:

  1. 使用以下命令添加包含稳定 Helm Charts 的存储库:
helm repo add stable https://kubernetes-charts.storage.googleapis.com/
  1. 要安装 WordPress,我们将运行以下命令:
helm install handsonakswp stable/wordpress

此操作将导致 Helm 安装详细说明在github.com/helm/charts/tree/master/stable/wordpress的图表。

Helm 安装需要一些时间,网站才能启动。让我们在网站加载时看一下一个关键概念,即 PersistentVolumeClaims。在介绍完这个概念后,我们将回过头来看看我们创建的网站。

PersistentVolumeClaims

一个进程需要计算、内存、网络和存储。在 guestbook 示例中,我们看到 Kubernetes 如何帮助我们抽象出计算、内存和网络。相同的 YAML 文件适用于所有云提供商,包括特定于云的公共负载均衡器的设置。WordPress 示例显示了最后一部分,即存储,是如何从底层云提供商中抽象出来的。

在这种情况下,WordPress Helm Chart 依赖于 MariaDB helm chart(github.com/helm/charts/tree/master/stable/mariadb)来进行数据库安装。

与无状态应用程序(如我们的前端)不同,MariaDB 需要对存储进行仔细处理。为了让 Kubernetes 处理有状态的工作负载,它有一个特定的对象称为 StatefulSet。StatefulSet(kubernetes.io/docs/concepts/workloads/controllers/statefulset/)类似于部署,具有顺序的额外能力和 Pod 的唯一性。这意味着 Kubernetes 将确保 Pod 及其存储被保持在一起。StatefulSets 的另一个帮助方式是一致地命名 StatefulSet 中的 Pod。Pod 的命名方式是<pod-name>-#,其中#从第一个 Pod 开始为0,第二个 Pod 为1

运行以下命令,您可以看到 MariaDB 附有可预测的编号,而 WordPress 部署附有随机编号:

kubectl get pods

这将生成图 3.18中显示的输出:

当执行 kubectl get pods 命令时,您将看到 MariaDB Pod 的可预测名称和 WordPress Pod 的随机字母数字名称。

图 3.18:显示 MariaDB Pod 的可预测编号,而 WordPress Pod 的随机名称

编号强调了部署 Pod 与 StatefulSet Pod 的短暂性质。

另一个区别是如何处理 pod 的删除。当删除部署的 pod 时,Kubernetes 会在任何可以的地方重新启动它,而当 StatefulSet 的 pod 被删除时,Kubernetes 只会在它曾经运行的节点上重新启动它。只有在节点从 Kubernetes 集群中移除时,它才会重新定位 pod。

通常,您会希望将存储附加到 StatefulSet。为了实现这一点,StatefulSet 需要一个持久卷。这个卷可以由许多机制支持(包括块,如 Azure Blob、EBS 和 iSCSI,以及网络文件系统,如 AFS、NFS 和 GlusterFS)。请参考 https://Kubernetes.io/docs/concepts/storage/volumes/#persistentvolumeclaim 获取更多信息。

StatefulSets 要求预先配置的卷或由PersistentVolumeClaimPVC)处理的动态配置的卷。在我们的示例中,我们使用了 PVC。PVC 提供了对底层存储机制的抽象。让我们看看 MariaDB Helm Chart 通过运行以下命令为我们做了什么:

kubectl get statefulsets

这将向我们展示类似于图 3.19的东西:

执行 kubectl get statefulsets 命令后,输出屏幕将显示一个处于 1/1 Ready 状态的 MariaDB Pod。

图 3.19:显示创建 MariaDB Pods 的 StatefulSet 的输出。

通过导出 StatefulSet 的 YAML 定义,让我们更深入地了解一下:

kubectl get statefulset -o yaml > mariadbss.yaml
code mariadbss.yaml

让我们看一下该 YAML 文件的最相关部分。代码已经被截断,只显示了最相关的部分:

1   apiVersion: v1
2   items:
3   - apiVersion: apps/v1
4     kind: StatefulSet
...
106           volumeMounts:
107           - mountPath: /bitnami/mariadb
108             name: data
...           
128     volumeClaimTemplates:
129     - metadata:
130         creationTimestamp: null
131         labels:
132           app: mariadb
133           component: master
134           heritage: Helm
135           release: handsonakswp
136         name: data
137       spec:
138         accessModes:
139         - ReadWriteOnce
140         resources:
141           requests:
142             storage: 8Gi
143         volumeMode: Filesystem
...

前面代码的大部分元素在部署中已经涵盖过了。在接下来的块中,我们将突出显示关键的不同之处,只看 PVC:

注意

PVC 可以被任何 Pod 使用,而不仅仅是 StatefulSet Pods。

让我们详细讨论前面代码的不同元素:

  • 第 4 行:此行指示了StatefulSet的声明。

  • 第 106-108 行:挂载定义为“数据”的卷,并将其挂载在/bitnami/mariadb路径下。

  • 第 128-143 行:声明 PVC。特别注意:

  • 第 136 行:此行将其命名为“数据”,在第 108 行重复使用。

  • 第 139 行:给出了访问模式ReadWriteOnce,这将创建块存储,在 Azure 上是一个磁盘。

  • 第 142 行:定义磁盘的大小。

根据前面的信息,Kubernetes 动态请求并将 8Gi 卷绑定到此 Pod。在这种情况下,使用了由 Azure 磁盘支持的默认动态存储 provisioner。动态 provisioner 是在创建集群时由 Azure 设置的。要查看集群上可用的存储类,可以运行以下命令:

kubectl get storageclass

这将显示类似于图 3.20的输出:

kubectl get storageclass 命令生成一个输出,您将看到两个存储类,即默认和托管高级。两者都是 provisioner 类型的 azure-disktype。

图 3.20:显示集群中不同存储类的输出

通过运行以下命令,我们可以获取有关 PVC 的更多详细信息:

kubectl get pvc

生成的输出显示在图 3.21中:

当您执行 kubectl get pvc 命令时,输出屏幕将显示每个 PVC 的名称、状态、卷、容量、访问模式、存储类和年龄。

图 3.21:集群中不同的 PVCs

当我们在 StatefulSet 描述中请求存储(第 128-143 行)时,Kubernetes 执行了 Azure-disk 特定的操作,以获取 8 GiB 存储的 Azure 磁盘。如果您复制 PVC 的名称并将其粘贴到 Azure 搜索栏中,您应该会找到已创建的磁盘:

当您将从上一个命令生成的 PVC 名称粘贴到栏中时,您将看到资源列中创建了一个磁盘。

图 3.22:获取与 PVC 关联的磁盘

PVC 的概念抽象了云提供商的存储细节。这使得相同的 Helm 模板可以在 Azure、AWS 或 GCP 上工作。在 AWS 上,它将由弹性块存储EBS)支持,在 GCP 上将由持久磁盘支持。

另外,请注意,可以在不使用 Helm 的情况下部署 PVC。

检查 WordPress 部署

在分析 PVC 之后,让我们再次检查 Helm 部署。我们可以使用以下命令检查部署的状态:

helm ls

这应该返回图 3.23中显示的输出:

执行 helm ls 命令时,输出屏幕将显示部署的状态为已部署。

图 3.23:Helm 显示我们的 WordPress 应用程序已部署

我们可以使用以下命令从 Helm 中获取更多信息:

helm status handsonakswp

这将返回图 3.24中显示的输出:

使用 helm status handsonakswp 命令,您可以获取有关应用程序的更多详细信息。

图 3.24:获取有关应用程序的更多详细信息

这表明我们的图表已成功部署。它还显示了如何连接到我们的网站的更多信息。我们现在不会使用这些步骤;我们将在第五章“处理 AKS 中的常见故障”中重新讨论这些步骤,在该部分中我们将介绍如何修复存储挂载问题。现在,我们将查看 Helm 为我们创建的所有内容:

kubectl get all

这将生成类似于图 3.25的输出:

图 3.25:执行 kubectl get all 命令会生成一个输出屏幕,显示 Helm 创建的对象,如 Pod、Service、Deployment、ReplicaSet 和 StatefulSet,以及每个对象的信息。您还将获得 WordPress 服务的外部 IP。

图 3.25:显示 Helm 创建的所有对象的输出

如果您还没有外部 IP,请等待几分钟,然后重试该命令。

然后,您可以继续连接到您的外部 IP 并访问您的 WordPress 网站。以下截图是生成的输出:

当您将外部 IP 连接到 WordPress 网站时,您将看到一个显示“Hello World!”并鼓励您开始撰写博客文章的屏幕。

图 3.26:连接到外部 IP 时显示的 WordPress 网站

为了确保在接下来的章节中不会遇到问题,让我们删除 WordPress 网站。可以通过以下方式完成:

helm delete handsonakswp

按设计,我们的 PVCs 不会被删除。这确保持久数据得到保留。由于我们没有任何持久数据,我们也可以安全地删除这些数据:

kubectl delete pvc --all

注意

在执行kubectl delete <object> --all时要非常小心,因为它会删除命名空间中的所有对象。这在生产集群上是不推荐的。

在本节中,您使用 Helm 部署了一个完整的 WordPress 网站。您还了解了 Kubernetes 如何使用 PVC 处理持久存储。

总结

在本章中,我们部署了两个应用程序。我们首先部署了 guestbook 应用程序。在部署过程中,我们查看了 Pods、ReplicaSets 和 deployments 的详细信息。我们还使用 ConfigMaps 进行动态配置。最后,我们了解了服务如何用于将流量路由到部署的应用程序。

我们部署的第二个应用是 WordPress 应用程序。我们通过 Helm 软件包管理器部署了它。作为部署的一部分,我们使用了 PVC,并探讨了它们在系统中的使用方式。

在下一章中,我们将探讨应用程序和集群本身的扩展。我们将首先看一下应用程序的手动和自动扩展,然后再看一下集群本身的手动和自动扩展。最后,我们将解释在 Kubernetes 上更新应用程序的不同方式。

第四章:构建可扩展的应用程序

在运行应用程序时,扩展和升级应用程序的能力至关重要。扩展是处理应用程序的额外负载所必需的,而升级是保持应用程序最新并能够引入新功能所必需的。

按需扩展是使用基于云的应用程序的关键好处之一。它还有助于优化应用程序的资源。如果前端组件遇到重负载,您可以单独扩展前端,同时保持相同数量的后端实例。您可以根据工作负载和高峰需求小时增加或减少所需的虚拟机VM)的数量/大小。本章将详细介绍两种扩展维度。

在本章中,我们将向您展示如何扩展我们在第三章 在 AKS 上部署应用程序中介绍的示例留言簿应用程序。我们将首先使用手动命令扩展此应用程序,然后我们将使用水平 Pod 自动缩放器(HPA)对其进行自动缩放。我们的目标是让您熟悉kubectl,这是管理在Azure Kubernetes ServiceAKS)上运行的应用程序的重要工具。在扩展应用程序本身之后,我们还将扩展集群。我们将首先手动扩展集群,然后使用集群自动缩放器自动扩展集群。此外,在本章中,您将简要介绍如何升级在 AKS 上运行的应用程序。

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

  • 扩展您的应用程序

  • 扩展您的集群

  • 升级您的应用程序

我们将从讨论在 AKS 上扩展应用程序时涉及的不同维度开始本章。

注意

在上一章中,我们在 Cloud Shell 中克隆了示例文件。如果您当时没有这样做,我们建议现在这样做:

git clone github.com/PacktPublishing/Hands-On-Kubernetes-on-Azure---Second-Edition/tree/SecondEdition

对于本章,请导航到Chapter04目录:

cd Chapter04

扩展您的应用程序

在 AKS 上运行的应用程序有两个扩展维度。第一个扩展维度是部署的 Pod 的数量,而 AKS 中的第二个扩展维度是集群中节点的数量。

通过向部署添加额外的 Pod,也称为扩展,您为部署的应用程序增加了额外的计算能力。您可以手动扩展应用程序,也可以让 Kubernetes 通过 HPA 自动处理这一点。HPA 将监视诸如 CPU 之类的指标,以确定是否需要向部署添加 Pod。

AKS 中的第二个扩展维度是集群中的节点数。集群中的节点数定义了集群上所有应用程序可用的 CPU 和内存量。您可以通过手动更改节点数来扩展集群,也可以使用集群自动缩放器自动扩展集群。集群自动缩放器将监视无法由于资源约束而无法调度的 Pod。如果无法调度 Pod,它将向集群添加节点,以确保您的应用程序可以运行。

本章将涵盖两个扩展维度。在本节中,您将学习如何扩展您的应用程序。首先,您将手动扩展您的应用程序,然后,您将自动扩展您的应用程序。

实施应用程序的扩展

为了演示手动扩展,让我们使用在上一章中使用的 guestbook 示例。按照以下步骤学习如何实施手动扩展:

  1. 通过在 Azure 命令行中运行kubectl create命令来安装 guestbook:
kubectl create -f guestbook-all-in-one.yaml
  1. 输入上述命令后,您应该在命令行输出中看到类似的内容,如图 4.1所示:当您执行该命令时,您的命令行输出将列出已创建的服务和部署。
图 4.1:启动 guestbook 应用程序
  1. 目前,没有任何服务是公开可访问的。我们可以通过运行以下命令来验证这一点:
kubectl get svc 
  1. 图 4.2显示没有任何服务具有外部 IP:输出屏幕将显示 External-IP 列为。这表示没有任何服务具有公共 IP。
图 4.2:显示没有任何服务具有公共 IP 的输出
  1. 为了测试我们的应用程序,我们将公开它。为此,我们将介绍一个新的命令,允许您在 Kubernetes 中编辑服务,而无需更改文件系统上的文件。要开始编辑,请执行以下命令:
kubectl edit service frontend
  1. 这将打开一个vi环境。导航到现在显示为type: ClusterIP(第 27 行),并将其更改为type: LoadBalancer,如图 4.3所示。要进行更改,请按I按钮,输入更改,按Esc按钮,输入:wq!,然后按Enter保存更改:输出显示第 27 行被 Type: LoadBalancer 替换。
图 4.3:将此行更改为 type: LoadBalancer
  1. 保存更改后,您可以观察服务对象,直到公共 IP 可用。要做到这一点,请输入以下内容:
kubectl get svc -w
  1. 更新 IP 地址可能需要几分钟时间。一旦看到正确的公共 IP,您可以通过按Ctrl + C(Mac 上为command + C)退出watch命令:使用 kubectl get svc -w 命令,前端服务的外部 IP 将从更改为实际的外部 IP 地址。
图 4.4:显示前端服务获得公共 IP
  1. 在浏览器导航栏中输入前面命令获取的 IP 地址,如下所示:http://<EXTERNAL-IP>/。其结果如图 4.5所示:在浏览器导航栏中输入前面命令获取的外部 IP。显示一个带有粗体字 Guestbook 的白屏。
图 4.5:浏览访客留言应用程序

熟悉的访客留言示例应该是可见的。这表明您已成功地公开访问了访客留言。

现在您已经部署了访客留言应用程序,可以开始扩展应用程序的不同组件。

扩展访客留言前端组件

Kubernetes 使我们能够动态地扩展应用程序的每个组件。在本节中,我们将向您展示如何扩展访客留言应用程序的前端。这将导致 Kubernetes 向部署添加额外的 Pods:

kubectl scale deployment/frontend --replicas=6

您可以设置要使用的副本数,Kubernetes 会处理其余的工作。您甚至可以将其缩减为零(用于重新加载配置的技巧之一,当应用程序不支持动态重新加载配置时)。要验证整体扩展是否正确工作,您可以使用以下命令:

kubectl get pods

这应该给您一个如图 4.6所示的输出:

执行 kubectl get pods 命令后,您将看到前端现在运行了六个 Pods。

图 4.6:扩展后访客留言应用程序中运行的不同 Pods

如您所见,前端服务扩展到了六个 Pod。Kubernetes 还将这些 Pod 分布在集群中的多个节点上。您可以使用以下命令查看此服务运行在哪些节点上:

kubectl get pods -o wide

这将生成以下输出:

执行 kubectl get pods -o wide 命令会显示 Pod 所在的节点。

图 4.7:显示 Pod 运行在哪些节点上。

在本节中,您已经看到了使用 Kubernetes 扩展 Pod 有多么容易。这种能力为您提供了一个非常强大的工具,不仅可以动态调整应用程序组件,还可以通过同时运行多个组件实例来提供具有故障转移能力的弹性应用程序。然而,您并不总是希望手动扩展您的应用程序。在下一节中,您将学习如何自动扩展您的应用程序。

使用 HPA

在您工作在集群上时,手动扩展是有用的。然而,在大多数情况下,您希望应用程序发生某种自动缩放。在 Kubernetes 中,您可以使用名为水平 Pod 自动缩放器HPA)的对象来配置部署的自动缩放。

HPA 定期监视 Kubernetes 指标,并根据您定义的规则自动缩放您的部署。例如,您可以配置 HPA 在应用程序的 CPU 利用率超过 50%时向部署添加额外的 Pod。

在本节中,您将配置 HPA 自动扩展应用程序的前端部分:

  1. 要开始配置,让我们首先手动将我们的部署缩减到 1 个实例:
kubectl scale deployment/frontend --replicas=1
  1. 接下来,我们将创建一个 HPA。通过输入code hpa.yaml在 Cloud Shell 中打开代码编辑器,并输入以下代码:
1  apiVersion: autoscaling/v2beta1
2  kind: HorizontalPodAutoscaler
3  metadata:
4    name: frontend-scaler
5  spec:
6    scaleTargetRef:
7      apiVersion: extensions/v1beta1
8      kind: Deployment
9      name: frontend
10   minReplicas: 1
11   maxReplicas: 10
12   metrics:
13   - type: Resource
14     resource:
15       name: cpu
16       targetAverageUtilization: 25

让我们来看看这个文件中配置了什么:

  • 第 2 行:在这里,我们定义了需要HorizontalPodAutoscaler

  • 第 6-9 行:这些行定义了我们要自动缩放的部署。

  • 第 10-11 行:在这里,我们配置了部署中的最小和最大 Pod 数。

  • 第 12-16 行:在这里,我们定义了 Kubernetes 将要监视的指标,以便进行扩展。

  1. 保存此文件,并使用以下命令创建 HPA:
kubectl create -f hpa.yaml

这将创建我们的自动缩放器。您可以使用以下命令查看您的自动缩放器:

kubectl get hpa

这将最初输出如图 4.8所示的内容:

执行 kubectl get hpa 显示已创建的水平 Pod 自动缩放器。目标显示为,表示 HPA 尚未完全准备好。

图 4.8:未知的目标显示 HPA 尚未准备好

HPA 需要几秒钟来读取指标。等待 HPA 返回的结果看起来类似于图 4.9中显示的输出:

执行 kubectl get hpa --watch 显示目标值从更改为此截图中的实际值 10%。

图 4.9:一旦目标显示百分比,HPA 就准备好了
  1. 现在,我们将继续做两件事:首先,我们将观察我们的 Pods,看看是否创建了新的 Pods。然后,我们将创建一个新的 shell,并为我们的系统创建一些负载。让我们从第一个任务开始——观察我们的 Pods:
kubectl get pods -w

这将持续监视创建或终止的 Pod。

现在让我们在一个新的 shell 中创建一些负载。在 Cloud Shell 中,点击按钮打开一个新的 shell:

单击工具栏上带有加号标志的图标。此按钮将打开一个新的 Cloud Shell。

图 4.10:使用此按钮打开一个新的 Cloud Shell

这将在浏览器中打开一个新的选项卡,其中包含 Cloud Shell 中的新会话。我们将从这个选项卡为我们的应用程序生成一些负载。

  1. 接下来,我们将使用一个名为hey的程序来生成这个负载。hey是一个发送负载到 Web 应用程序的小程序。我们可以使用以下命令安装和运行hey
export GOPATH=~/go
export PATH=$GOPATH/bin:$PATH
go get -u github.com/rakyll/hey
hey -z 20m http://<external-ip>

hey程序现在将尝试创建多达 2000 万个连接到我们的前端。这将在我们的系统上生成 CPU 负载,这将触发 HPA 开始扩展我们的部署。这将需要几分钟才能触发扩展操作,但在某个时刻,您应该看到创建多个 Pod 来处理额外的负载,如图 4.11所示:

执行 kubectl get pods -w 将显示正在创建的前端新 Pod。您将看到新的 Pod 从 Pending 状态更改为 ContainerCreating 状态再到 Running 状态。

图 4.11:HPA 启动新的 Pod

在这一点上,您可以继续通过按Ctrl + C(Mac 上的command + C)来终止hey程序。

  1. 让我们通过运行以下命令来更仔细地查看我们的 HPA 所做的事情:
kubectl describe hpa

我们可以在“描述”操作中看到一些有趣的点,如图 4.12所示:

执行 kubectl describe hpa 命令将生成 HPA 的详细视图。您将看到资源负载,一个显示 TooManyReplicas 的消息,HPA 将从 1 扩展到 4,然后到 8,最后到 10。

图 4.12:HPA 的详细视图

图 4.12中的注释解释如下:

1:这向您显示当前的 CPU 利用率(132%)与期望值(25%)的对比。当前的 CPU 利用率在您的情况下可能会有所不同。

2:这向您显示当前期望的副本数高于我们配置的实际最大值。这确保了单个部署不会消耗集群中的所有资源。

3:这向您显示了 HPA 所采取的扩展操作。它首先将部署扩展到 4 个,然后扩展到 8 个,然后扩展到 10 个 Pod。

  1. 如果您等待几分钟,HPA 应该开始缩减。您可以使用以下命令跟踪此缩减操作:
kubectl get hpa -w

这将跟踪 HPA 并向您显示部署的逐渐缩减,如图 4.13所示:

当您执行 kubectl get hpa -w 命令时,您将看到副本数量逐渐减少。

图 4.13:观察 HPA 的缩减
  1. 在我们进入下一节之前,让我们清理一下在本节中创建的资源:
kubectl delete -f hpa.yaml
kubectl delete -f guestbook-all-in-one.yaml

在本节中,我们首先手动,然后自动扩展了我们的应用程序。但是,集群资源是静态的;我们在一个两节点的集群上运行了这个。在许多情况下,您可能也会在集群本身耗尽资源。在下一节中,我们将处理这个问题,并解释如何自己扩展您的 AKS 集群。

扩展您的集群

在上一节中,我们处理了在集群顶部运行的应用程序的扩展。在本节中,我们将解释如何扩展您正在运行的实际集群。我们将首先讨论如何手动扩展您的集群。我们将从将我们的集群缩减到一个节点开始。然后,我们将配置集群自动缩放器。集群自动缩放器将监视我们的集群,并在无法在我们的集群上安排的 Pod 时进行扩展。

手动扩展您的集群

您可以通过为集群设置静态节点数来手动扩展您的 AKS 集群。您可以通过 Azure 门户或命令行来扩展您的集群。

在本节中,我们将向您展示如何通过将集群缩减到一个节点来手动扩展您的集群。这将导致 Azure 从您的集群中移除一个节点。首先,即将被移除的节点上的工作负载将被重新调度到其他节点上。一旦工作负载安全地重新调度,节点将从您的集群中移除,然后 VM 将从 Azure 中删除。

要扩展您的集群,请按照以下步骤操作:

  1. 打开 Azure 门户并转到您的集群。一旦到达那里,转到节点池并点击节点计数下面的数字,如图 4.14所示:在 Azure 门户上打开您的集群后,转到左侧屏幕上的导航窗格中的节点池选项卡。点击该选项卡。您将看到节点的详细信息。点击节点计数选项卡上的数字 2 以更改节点数。
图 4.14:手动扩展集群
  1. 这将打开一个弹出窗口,其中将提供扩展集群的选项。在我们的示例中,我们将将集群缩减到一个节点,如图 4.15所示:当您点击节点计数选项卡时,将弹出一个窗口,让您选择扩展您的集群。将其缩减到一个节点。
图 4.15:确认新集群大小的弹出窗口
  1. 点击屏幕底部的应用按钮以保存这些设置。这将导致 Azure 从您的集群中移除一个节点。这个过程大约需要 5 分钟才能完成。您可以通过点击 Azure 门户顶部的通知图标来跟踪进度,如下所示:节点缩减的过程需要一些时间。要查看进度,请点击工具栏中的铃铛图标以打开通知。
图 4.16:可以通过 Azure 门户中的通知来跟踪集群的扩展

一旦这个缩减操作完成,我们将在这个小集群上重新启动我们的 guestbook 应用程序:

kubectl create -f guestbook-all-in-one.yaml

在下一节中,我们将扩展 guestbook,以便它不再在我们的小集群上运行。然后,我们将配置集群自动缩放器来扩展我们的集群。

使用集群自动缩放器扩展您的集群

在本节中,我们将探讨集群自动缩放器。集群自动缩放器将监视集群中的部署,并根据您的应用程序需求来扩展您的集群。集群自动缩放器会监视集群中由于资源不足而无法被调度的 Pod 的数量。我们将首先强制我们的部署有无法被调度的 Pod,然后我们将配置集群自动缩放器自动扩展我们的集群。

为了强制我们的集群资源不足,我们将手动扩展redis-slave部署。要做到这一点,请使用以下命令:

kubectl scale deployment redis-slave --replicas 5

您可以通过查看我们集群中的 Pod 来验证此命令是否成功:

kubectl get pods

这应该显示类似于图 4.17中生成的输出:

执行 kubectl get pods 将显示四个处于挂起状态的 Pod。这意味着它们无法被调度到节点上。

图 4.17:五个 Pod 中有四个处于挂起状态,意味着它们无法被调度

如您所见,我们现在有四个处于“挂起”状态的 Pod。在 Kubernetes 中,“挂起”状态意味着该 Pod 无法被调度到节点上。在我们的情况下,这是由于集群资源不足造成的。

我们现在将配置集群自动缩放器自动扩展我们的集群。与上一节中的手动扩展一样,您可以通过两种方式配置集群自动缩放器。您可以通过 Azure 门户配置它,类似于我们进行手动扩展的方式,或者您可以使用命令行界面(CLI)进行配置。在本例中,我们将使用 CLI 来启用集群自动缩放器。以下命令将为我们的集群配置集群自动缩放器:

az aks nodepool update --enable-cluster-autoscaler \
  -g rg-handsonaks --cluster-name handsonaks \
  --name agentpool --min-count 1 --max-count 3

此命令在我们集群中的节点池上配置了集群自动缩放器。它将其配置为最少一个节点和最多三个节点。这将花费几分钟来配置。

配置了集群自动缩放器后,您可以使用以下命令来观察集群中节点的数量:

kubectl get nodes -w

新节点出现并在集群中变为Ready大约需要 5 分钟。一旦新节点处于Ready状态,您可以通过按下Ctrl + C(Mac 上的command + C)来停止观察节点。您应该看到类似于图 4.18中的输出:

kubectl get nodes -w 命令显示新节点被添加到集群,并且状态从 NotReady 变为 Ready。

图 4.18:新节点加入集群

新节点应该确保我们的集群有足够的资源来调度扩展的redis-slave部署。要验证这一点,请运行以下命令来检查 Pod 的状态:

kubectl get pods

这应该显示所有处于Running状态的 Pod,如下所示:

执行 kubectl get pods 命令显示所有 Pod 的状态为 Running。

图 4.19:所有 Pod 现在都处于 Running 状态

我们现在将清理我们创建的资源,禁用集群自动缩放器,并确保我们的集群在接下来的示例中有两个节点。要做到这一点,请使用以下命令:

kubectl delete -f guestbook-all-in-one.yaml
az aks nodepool update --disable-cluster-autoscaler \
  -g rg-handsonaks --cluster-name handsonaks --name agentpool
az aks nodepool scale --node-count 2 -g rg-handsonaks \
  --cluster-name handsonaks --name agentpool

注意

上一个示例中的最后一个命令将在集群已经有两个节点的情况下显示错误。您可以安全地忽略此错误。

在本节中,我们首先手动缩减了我们的集群,然后我们使用了集群自动缩放器来扩展我们的集群。我们首先使用 Azure 门户手动扩展了集群,然后我们还使用了 Azure CLI 来配置集群自动缩放器。在下一节中,我们将探讨如何升级在 AKS 上运行的应用程序。

升级您的应用程序

在 Kubernetes 中使用部署使升级应用程序成为一个简单的操作。与任何升级一样,如果出现问题,您应该有良好的回退。您将遇到的大多数问题将发生在升级过程中。云原生应用程序应该使处理这些问题相对容易,如果您有一个非常强大的开发团队,他们拥抱 DevOps 原则是可能的。

DevOps 报告(services.google.com/fh/files/misc/state-of-devops-2019.pdf)已经多年报告说,具有高软件部署频率的公司在其应用程序中具有更高的可用性和稳定性。这可能看起来违反直觉,因为进行软件部署会增加问题的风险。然而,通过更频繁地部署并使用自动化的 DevOps 实践进行部署,您可以限制软件部署的影响。

在 Kubernetes 集群中,我们可以进行多种方式的更新。在本节中,我们将探讨以下更新 Kubernetes 资源的方式:

  • 通过更改 YAML 文件进行升级

  • 使用kubectl edit进行升级

  • 使用kubectl patch进行升级

  • 使用 Helm 进行升级

我们将在下一节中描述的方法非常适用于无状态应用程序。如果您在任何地方存储了状态,请确保在尝试任何操作之前备份该状态。

让我们通过进行第一种类型的升级来开始本节:更改 YAML 文件。

通过更改 YAML 文件进行升级

为了升级 Kubernetes 服务或部署,我们可以更新实际的 YAML 定义文件,并将其应用到当前部署的应用程序中。通常,我们使用kubectl create来创建资源。同样,我们可以使用kubectl apply来对资源进行更改。

部署检测更改(如果有)并将Running状态与期望状态匹配。让我们看看这是如何完成的:

  1. 我们从我们的留言簿应用程序开始,以演示这个例子:
kubectl create -f guestbook-all-in-one.yaml
  1. 几分钟后,所有的 Pod 应该都在运行。让我们通过将服务从ClusterIP更改为LoadBalancer来执行我们的第一个升级,就像我们在本章前面所做的那样。然而,现在我们将编辑我们的 YAML 文件,而不是使用kubectl edit。使用以下命令编辑 YAML 文件:
code guestbook-all-in-one.yaml

取消注释此文件中的第 108 行,将类型设置为LoadBalancer并保存文件。如图 4.20所示:

屏幕显示了编辑后的 YAML 文件。现在在第 108 行显示了类型为 LoadBalancer。

图 4.20:更改 guestbook-all-in-one YAML 文件中的这一行
  1. 按照以下代码进行更改:
kubectl apply -f guestbook-all-in-one.yaml
  1. 您现在可以使用以下命令获取服务的公共 IP:
kubectl get svc

等几分钟,您应该会看到 IP,就像图 4.21中显示的那样:

当您执行 kubectl get svc 命令时,您会看到只有前端服务有外部 IP。

图 4.21:显示公共 IP 的输出
  1. 现在我们将进行另一个更改。我们将把第 133 行的前端图像从image: gcr.io/google-samples/gb-frontend:v4降级为以下内容:
image: gcr.io/google-samples/gb-frontend:v3

通过使用这个熟悉的命令在编辑器中打开留言簿应用程序,可以进行此更改:

code guestbook-all-in-one.yaml
  1. 运行以下命令执行更新并观察 Pod 更改:
kubectl apply -f guestbook-all-in-one.yaml && kubectl get pods -w
  1. 这将生成以下输出:执行 kubectl apply -f guestbook-all-in-one.yaml && kubectl get pods -w 命令会生成一个显示 Pod 更改的输出。您将看到从新的 ReplicaSet 创建的 Pod。
图 4.22:从新的 ReplicaSet 创建的 Pod

在这里你可以看到旧版本的 Pod(基于旧的 ReplicaSet)被终止,而新版本被创建。

  1. 运行kubectl get events | grep ReplicaSet将显示部署使用的滚动更新策略,以更新前端图像如下:输出将突出显示所有与 ReplicaSet 相关的事件。
图 4.23:监视 Kubernetes 事件并筛选只看到与 ReplicaSet 相关的事件

注意

在上面的示例中,我们使用了管道—由|符号表示—和grep命令。在 Linux 中,管道用于将一个命令的输出发送到另一个命令的输入。在我们的情况下,我们将kubectl get events的输出发送到grep命令。grep是 Linux 中用于过滤文本的命令。在我们的情况下,我们使用grep命令只显示包含单词 ReplicaSet 的行。

您可以在这里看到新的 ReplicaSet 被扩展,而旧的 ReplicaSet 被缩减。您还将看到前端有两个 ReplicaSets,新的 ReplicaSet 逐个替换另一个 Pod:

kubectl get replicaset

这将显示如图 4.24所示的输出:

使用 kubectl get replicaset 命令,您可以看到两个不同的 ReplicaSets。

图 4.24:两个不同的 ReplicaSets
  1. Kubernetes 还将保留您的部署历史。您可以使用此命令查看部署历史:
kubectl rollout history deployment frontend

这将生成如图 4.25所示的输出:

图 4.25:输出屏幕显示了应用程序的历史。它显示了修订次数、更改和更改的原因。

图 4.25:应用程序的部署历史
  1. 由于 Kubernetes 保留了我们部署的历史记录,这也使得回滚成为可能。让我们对部署进行回滚:
kubectl rollout undo deployment frontend

这将触发回滚。这意味着新的 ReplicaSet 将被缩减为零个实例,而旧的 ReplicaSet 将再次扩展为三个实例。我们可以使用以下命令来验证这一点:

kubectl get replicaset

产生的输出如图 4.26所示:

图 4.26:执行 kubectl get rs 命令显示两个前端 ReplicaSets。一个没有 pod,另一个有 3 个 pod。

图 4.26:旧的 ReplicaSet 现在有三个 Pod,而新的 ReplicaSet 被缩减为零

这向我们展示了,正如预期的那样,旧的 ReplicaSet 被缩减为三个实例,新的 ReplicaSet 被缩减为零个实例。

  1. 最后,让我们再次通过运行kubectl delete命令进行清理:
kubectl delete -f guestbook-all-in-one.yaml

恭喜!您已完成了应用程序的升级和回滚到先前版本。

在此示例中,您已使用kubectl apply对应用程序进行更改。您也可以类似地使用kubectl edit进行更改,这将在下一节中探讨。

使用 kubectl edit 升级应用程序

我们可以通过使用kubectl edit对运行在 Kubernetes 之上的应用程序进行更改。您在本章中先前使用过这个。运行kubectl edit时,vi编辑器将为您打开,这将允许您直接对 Kubernetes 中的对象进行更改。

让我们重新部署我们的 guestbook 应用程序,而不使用公共负载均衡器,并使用kubectl创建负载均衡器:

  1. 您将开始部署 guestbook 应用程序:
kubectl create -f guestbook-all-in-one.yaml
  1. 要开始编辑,请执行以下命令:
kubectl edit service frontend
  1. 这将打开一个vi环境。导航到现在显示为type: ClusterIP(第 27 行),并将其更改为type: LoadBalancer,如图 4.27所示。要进行更改,请按I按钮,输入更改内容,按Esc按钮,输入:wq!,然后按Enter保存更改:输出显示第 27 行被 Type: LoadBalancer 替换。
图 4.27:将此行更改为类型:LoadBalancer
  1. 保存更改后,您可以观察服务对象,直到公共 IP 可用。要做到这一点,请输入以下内容:
kubectl get svc -w
  1. 它将花费几分钟时间来显示更新后的 IP。一旦看到正确的公共 IP,您可以通过按Ctrl + C(Mac 上为command + C)退出watch命令

这是使用kubectl edit对 Kubernetes 对象进行更改的示例。此命令将打开一个文本编辑器,以交互方式进行更改。这意味着您需要与文本编辑器交互以进行更改。这在自动化环境中不起作用。要进行自动化更改,您可以使用kubectl patch命令。

使用 kubectl patch 升级应用程序

在先前的示例中,您使用文本编辑器对 Kubernetes 进行更改。在这个示例中,您将使用kubectl patch命令对 Kubernetes 上的资源进行更改。patch命令在自动化系统中特别有用,例如在脚本中或在持续集成/持续部署系统中。

有两种主要方式可以使用kubectl patch,一种是创建一个包含更改的文件(称为补丁文件),另一种是提供内联更改。我们将探讨这两种方法。首先,在这个例子中,我们将使用补丁文件将前端的图像从v4更改为v3

  1. 通过创建一个名为frontend-image-patch.yaml的文件来开始这个例子:
code frontend-image-patch.yaml
  1. 在该文件中使用以下文本作为补丁:
spec:
  template:
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google-samples/gb-frontend:v3

此补丁文件使用与典型 YAML 文件相同的 YAML 布局。补丁文件的主要特点是它只需要包含更改,而不必能够部署整个资源。

  1. 要应用补丁,请使用以下命令:
kubectl patch deployment frontend --patch "$(cat frontend-image-patch.yaml)"

此命令执行两件事:首先,它读取frontend-image-patch.yaml文件,然后将其传递给patch命令以执行更改。

  1. 您可以通过描述前端部署并查找Image部分来验证更改:
kubectl describe deployment frontend

这将显示如下输出:

执行 Patch 命令后,您可以使用 kubectl describe deployment frontend 命令来验证更改。您应该看到新图像的路径。

图 4.28:补丁后,我们正在运行旧图像

这是使用patch命令使用补丁文件的一个例子。您还可以在命令行上直接应用补丁,而不创建 YAML 文件。在这种情况下,您将以 JSON 而不是 YAML 描述更改。

让我们通过一个例子来说明,我们将图像更改恢复到v4

  1. 运行以下命令将图像补丁回到v4
kubectl patch deployment frontend --patch='{"spec":{"template":{"spec":{"containers":[{"name":"php-redis","image":"gcr.io/google-samples/gb-frontend:v4"}]}}}}'
  1. 您可以通过描述部署并查找Image部分来验证此更改:
kubectl describe deployment frontend

这将显示如图 4.29 所示的输出:

应用另一个补丁命令后,您将看到图像的新版本。

图 4.29:应用另一个补丁后,我们再次运行新版本

在我们继续下一个例子之前,让我们从集群中删除 guestbook 应用程序:

kubectl delete -f guestbook-all-in-one.yaml

到目前为止,您已经探索了升级 Kubernetes 应用程序的三种方式。首先,您对实际的 YAML 文件进行了更改,并使用kubectl apply应用了这些更改。之后,您使用了kubectl editkubectl patch进行了更多更改。在本章的最后一节中,我们将使用 Helm 来升级我们的应用程序。

使用 Helm 升级应用程序

本节将解释如何使用 Helm 操作符执行升级:

  1. 运行以下命令:
helm install wp stable/wordpress

我们将强制更新WordPress容器的图像。让我们首先检查当前图像的版本:

kubectl describe statefulset wp-mariadb | grep Image

在我们的情况下,图像版本为10.3.21-debian-10-r0如下:

输出显示 10.3.21-debian-10-r0 作为 StatefulSet 的当前版本。

图 4.30:获取 StatefulSet 的当前图像

让我们看一下来自hub.docker.com/r/bitnami/mariadb/tags的标签,并选择另一个标签。在我们的情况下,我们将选择10.3.22-debian-10-r9标签来更新我们的 StatefulSet。

然而,为了更新 MariaDB 容器图像,我们需要获取服务器的 root 密码和数据库的密码。我们可以通过以下方式获取这些密码:

kubectl get secret wp-mariadb -o yaml

这将生成一个如图 4.31所示的输出:

输出将显示 MariaDB 的密码和 root 密码的 base64 编码版本,我们需要更新容器图像。

图 4.31:MariaDB 使用的加密密码

为了获取解码后的密码,请使用以下命令:

echo "<password>" | base64 -d

这将向我们显示解码后的 root 密码和解码后的数据库密码。

  1. 我们可以使用 Helm 更新图像标签,然后使用以下命令观察 Pod 的更改:
helm upgrade wp stable/wordpress --set mariadb.image.tag=10.3.21-debian-10-r1,mariadb.rootUser.password=<decoded password>,mariadb.db.password=<decoded db password> && kubectl get pods -w

这将更新我们的 MariaDB 的图像并启动一个新的 Pod。在新的 Pod 上运行describe并使用grep查找Image将向我们显示新的图像版本:

kubectl describe pod wp-mariadb-0 | grep Image

这将生成一个如图 4.32所示的输出:

执行 kubectl describe pod wp-mariadb-0 | grep Image 命令将显示图像的新版本。

图 4.32:显示新图像
  1. 最后,通过运行以下命令进行清理:
helm delete wp
kubectl delete pvc --all
kubectl delete pv --all

因此,我们已经使用 Helm 升级了我们的应用程序。正如您在本例中所看到的,使用 Helm 进行升级可以通过使用--set运算符来完成。这使得使用 Helm 进行升级和多次部署变得非常高效。

总结

这是一个充满大量信息的章节。我们的目标是向您展示如何使用 Kubernetes 扩展部署。我们通过向您展示如何创建应用程序的多个实例来实现这一点。

我们开始这一章是通过研究如何定义负载均衡器的使用,并利用 Kubernetes 中的部署规模功能来实现可伸缩性。通过这种类型的可伸缩性,我们还可以通过使用负载均衡器和多个无状态应用程序实例来实现故障转移。我们还研究了如何使用 HPA 根据负载自动扩展我们的部署。

之后,我们还研究了如何扩展集群本身。首先,我们手动扩展了我们的集群,然后我们使用了集群自动缩放器根据应用程序需求来扩展我们的集群。

我们通过研究不同的方法来升级已部署的应用程序来完成了这一章。首先,我们探讨了手动更新 YAML 文件。然后,我们深入研究了两个额外的kubectl命令(editpatch),可以用来进行更改。最后,我们展示了如何使用 Helm 来执行这些升级。

在下一章中,我们将看到在部署应用程序到 AKS 时可能遇到的常见故障以及如何修复它们。

第五章:在 AKS 中处理常见故障

Kubernetes 是一个具有许多工作部分的分布式系统。AKS 为您抽象了大部分内容,但您仍有责任知道在发生不良情况时应该去哪里寻找以及如何做出响应。Kubernetes 会自动处理大部分故障;然而,您会遇到需要手动干预的情况。

在部署在 AKS 之上的应用程序中,有两个可能出现问题的领域。要么是集群本身出现问题,要么是部署在集群之上的应用程序出现问题。本章专注于集群问题。集群可能出现多种问题。

第一种可能出现的问题是集群中的节点可能变得不可用。这可能是由于 Azure 基础设施故障或虚拟机本身出现问题,例如操作系统崩溃。无论哪种情况,Kubernetes 都会监视集群中的节点故障,并将自动恢复。您将在本章中看到这个过程。

Kubernetes 集群中的第二个常见问题是资源不足的故障。这意味着您尝试部署的工作负载需要的资源超过了集群上可用的资源。您将学习如何监视这些信号以及如何解决这些问题。

另一个常见问题是挂载存储出现问题,这发生在节点变得不可用时。当 Kubernetes 中的节点变得不可用时,Kubernetes 不会分离附加到此失败节点的磁盘。这意味着这些磁盘不能被其他节点上的工作负载使用。您将看到一个实际的例子,并学习如何从这种故障中恢复。

在本章中,我们将深入研究以下故障模式:

  • 处理节点故障

  • 解决资源不足的故障

  • 处理存储挂载问题

在这一章中,您将了解常见的故障场景,以及针对这些场景的解决方案。首先,我们将介绍节点故障。

注意:

参考Kubernetes the Hard Waygithub.com/kelseyhightower/kubernetes-the-hard-way),一个优秀的教程,了解 Kubernetes 构建的基础。对于 Azure 版本,请参考Kubernetes the Hard Way – Azure Translationgithub.com/ivanfioravanti/kubernetes-the-hard-way-on-azure)。

处理节点故障

有意(为了节省成本)或无意中,节点可能会宕机。当这种情况发生时,您不希望在凌晨 3 点接到系统宕机的电话。Kubernetes 可以自动处理节点故障时的工作负载迁移。在这个练习中,我们将部署 guestbook 应用程序,并将在我们的集群中关闭一个节点,看看 Kubernetes 的响应是什么:

  1. 确保您的集群至少有两个节点:
kubectl get nodes

这应该生成一个如图 5.1所示的输出:

执行 kubectl get nodes 命令会显示一个带有两个节点的输出。这两个节点的状态为 Ready。

图 5.1:确保您的集群中有两个正在运行的节点

如果您的集群中没有两个节点,请在 Azure 门户中查找您的集群,导航到节点池,然后单击节点计数。您可以将其扩展到2个节点,如图 5.2所示:

单击 Azure 门户左侧的导航窗格中的节点池选项卡。这将显示给您几个选项。转到节点计数选项。单击它以将此计数扩展到两个节点。

图 5.2:扩展集群
  1. 作为本章的示例应用程序,我们将使用 guestbook 应用程序。部署此应用程序的 YAML 文件已在本章的源代码中提供(guestbook-all-in-one.yaml)。要部署 guestbook 应用程序,请使用以下命令:
kubectl create -f guestbook-all-in-one.yaml
  1. 我们将再次为服务提供公共 IP,就像在之前的章节中一样。要开始编辑,请执行以下命令:
kubectl edit service frontend
  1. 这将打开一个vi环境。导航到现在显示为type: ClusterIP(第 27 行)的行,并将其更改为type: LoadBalancer。要进行更改,请按I按钮进入插入模式,输入更改,然后按Esc按钮,输入:wq!,然后按Enter保存更改。

  2. 更改保存后,您可以观察service对象,直到公共 IP 变为可用。要做到这一点,请输入以下内容:

kubectl get svc -w
  1. 这将花费几分钟的时间来显示更新后的 IP。图 5.3代表了服务的公共 IP。一旦您看到正确的公共 IP,您可以通过按Ctrl + C(Mac 上为command + C)退出 watch 命令:输出显示前端服务将其外部 IP 从更改为实际 IP。
图 5.3:获取服务的公共 IP
  1. 转到http://<EXTERNAL-IP>,如图 5.4所示:一旦在浏览器的地址栏中输入公共 IP,它将打开一个带有粗体字“Guestbook”的白屏。这表明您的应用程序现在正在运行。
图 5.4:确保应用程序正在运行
  1. 让我们看看当前正在运行的 Pods 使用以下代码:
kubectl get pods -o wide

这将生成如图 5.5所示的输出:

当您执行 kubectl get pods -o wide 命令时,显示的输出将显示 Pods 分布在节点 0 和 1 之间。

图 5.5:我们的 Pods 分布在节点 0 和节点 1 之间

这显示我们的工作负载分布在节点 0 和节点 1 之间。

  1. 在这个例子中,我们想演示 Kubernetes 如何处理节点故障。为了演示这一点,我们将关闭集群中的一个节点。在这种情况下,我们要造成最大的破坏,所以让我们关闭节点 1(您可以选择任何节点 - 出于说明目的,这并不重要):

要关闭此节点,请在 Azure 搜索栏中查找我们集群中的虚拟机规模集,如图 5.6所示:

请在 Azure 搜索栏中键入 vmss 以关闭节点。

图 5.6:查找托管集群的 VMSS

导航到规模集的刀片后,转到实例视图,选择要关闭的实例,然后点击分配按钮,如图 5.7所示:

单击 Azure 门户中导航窗格中的“实例”选项卡。这将显示实例的数量。选择要关闭的实例。单击出现在地址栏中的取消分配图标。

图 5.7:关闭节点 1

这将关闭我们的节点。要查看 Kubernetes 如何与您的 Pods 交互,可以通过以下命令观察集群中的 Pods:

kubectl get pods -o wide -w
  1. 要验证您的应用程序是否可以继续运行,可以选择运行以下命令,每 5 秒钟点击一次 guestbook 前端并获取 HTML。建议在新的 Cloud Shell 窗口中打开此命令:
while true; do curl -m 1 http://<EXTERNAl-IP>/ ; sleep 5; done

注意

上述命令将一直调用您的应用程序,直到您按下Ctrl + C(Mac 上的command + C)。可能会有间歇时间您收不到回复,这是可以预期的,因为 Kubernetes 需要几分钟来重新平衡系统。

添加一些留言板条目,看看当你导致节点关闭时它们会发生什么。这将显示一个如图 5.8所示的输出:

使用留言板应用程序写下几条消息。

图 5.8:在留言板中写下几条消息

你所看到的是你所有珍贵的消息都消失了!这显示了在节点故障的情况下拥有PersistentVolumeClaimsPVCs)对于任何你希望存活的数据的重要性,在我们的应用程序中并非如此。你将在本章的最后一节中看到一个例子。

  1. 过一会儿,观察 Pods 的输出应该显示额外的输出,告诉你 Pods 已经在健康的主机上重新调度,如图 5.9所示:这将显示一个输出,显示了从失败节点重新创建的 Pods。
图 5.9:从失败节点重新创建的 Pods

你在这里看到的是:

  • 一个前端 Pod 在主机 1 上运行时被终止,因为主机变得不健康。

  • 一个新的前端 Pod 被创建在主机 0 上。这经历了PendingContainerCreating,然后是Running这些阶段。

注意

Kubernetes 在重新调度 Pods 之前察觉到主机不健康。如果你运行kubectl get nodes,你会看到节点 1 处于NotReady状态。Kubernetes 中有一个叫做 pod-eviction-timeout 的配置,它定义了系统等待在健康主机上重新调度 Pods 的时间。默认值是 5 分钟。

在本节中,你学会了 Kubernetes 如何自动处理节点故障,通过在健康节点上重新创建 Pods。在下一节中,你将学习如何诊断和解决资源耗尽的问题。

解决资源耗尽的问题

Kubernetes 集群可能出现的另一个常见问题是集群资源耗尽。当集群没有足够的 CPU 或内存来调度额外的 Pods 时,Pods 将被卡在Pending状态。

Kubernetes 使用requests来计算某个 Pod 需要多少 CPU 或内存。我们的留言板应用程序为所有部署定义了请求。如果你打开guestbook-all-in-one.yaml文件,你会看到redis-slave部署的以下内容:

...
63  kind: Deployment
64  metadata:
65    name: redis-slave
...
83          resources:
84            requests:
85              cpu: 100m
86              memory: 100Mi
...

本节解释了redis-slave部署的每个 Pod 都需要100m的 CPU 核心(100 毫或 10%)和 100MiB(Mebibyte)的内存。在我们的 1 个 CPU 集群(关闭节点 1),将其扩展到 10 个 Pods 将导致可用资源出现问题。让我们来看看这个:

注意

在 Kubernetes 中,您可以使用二进制前缀表示法或基数 10 表示法来指定内存和存储。二进制前缀表示法意味着使用 KiB(kibibyte)表示 1024 字节,MiB(mebibyte)表示 1024 KiB,Gib(gibibyte)表示 1024 MiB。基数 10 表示法意味着使用 kB(kilobyte)表示 1000 字节,MB(megabyte)表示 1000 kB,GB(gigabyte)表示 1000 MB。

  1. 让我们首先将redis-slave部署扩展到 10 个 Pods:
kubectl scale deployment/redis-slave --replicas=10
  1. 这将导致创建一对新的 Pod。我们可以使用以下命令来检查我们的 Pods:
kubectl get pods

这将生成以下输出:

使用 kubectl get pods 命令,您可以检查您的 Pods。输出将生成一些状态为 Pending 的 Pods。

图 5.10:如果集群资源不足,Pods 将进入 Pending 状态

这里突出显示了一个处于Pending状态的 Pod。

  1. 我们可以使用以下命令获取有关这些待处理 Pods 的更多信息:
kubectl describe pod redis-slave-<pod-id>

这将显示更多细节。在describe命令的底部,您应该看到类似于图 5.11中显示的内容:

输出显示了来自 default-scheduler 的 FailedSchedulingmessage 事件。详细消息显示“0/2 个节点可用:1 个 CPU 不足,1 个节点有 Pod 无法容忍的污点。”

图 5.11:Kubernetes 无法安排此 Pod

它向我们解释了两件事:

  • 其中一个节点的 CPU 资源已经用完。

  • 其中一个节点有一个 Pod 无法容忍的污点。这意味着NotReady的节点无法接受 Pods。

  1. 我们可以通过像图 5.12中所示的方式启动节点 1 来解决这个容量问题。这可以通过类似于关闭过程的方式完成:您可以使用与关闭相同的过程来启动节点。单击导航窗格中的实例选项卡。选择要启动的节点。最后,单击工具栏中的“启动”按钮。
图 5.12:重新启动节点 1
  1. 其他节点再次在 Kubernetes 中变得可用需要几分钟的时间。如果我们重新执行先前 Pod 上的describe命令,我们将看到类似于图 5.13所示的输出:描述 Pod 的事件输出显示,经过一段时间,调度程序将 Pod 分配给新节点,并显示拉取图像的过程,以及创建和启动容器的过程。
图 5.13:当节点再次可用时,其他 Pod 将在新节点上启动。

这表明在节点 1 再次变得可用后,Kubernetes 将我们的 Pod 调度到该节点,然后启动容器。

在本节中,我们学习了如何诊断资源不足的错误。我们通过向集群添加另一个节点来解决了这个错误。在我们继续进行最终故障模式之前,我们将清理一下 guestbook 部署。

注意

第四章中,扩展您的应用程序,我们讨论了集群自动缩放器。集群自动缩放器将监视资源不足的错误,并自动向集群添加新节点。

让我们通过运行以下delete命令来清理一下:

kubectl delete -f guestbook-all-in-one.yaml

到目前为止,我们已经讨论了 Kubernetes 集群中节点的两种故障模式。首先,我们讨论了 Kubernetes 如何处理节点离线以及系统如何将 Pod 重新调度到工作节点上。之后,我们看到了 Kubernetes 如何使用请求来调度节点上的 Pod,以及当集群资源不足时会发生什么。在接下来的部分,我们将讨论 Kubernetes 中的另一种故障模式,即当 Kubernetes 移动带有 PVC 的 Pod 时会发生什么。

修复存储挂载问题

在本章的前面,您注意到当 Redis 主节点移动到另一个节点时,guestbook 应用程序丢失了数据。这是因为该示例应用程序没有使用任何持久存储。在本节中,我们将介绍当 Kubernetes 将 Pod 移动到另一个节点时,如何使用 PVC 来防止数据丢失的示例。我们将向您展示 Kubernetes 移动带有 PVC 的 Pod 时会发生的常见错误,并向您展示如何修复此错误。

为此,我们将重用上一章中的 WordPress 示例。在开始之前,让我们确保集群处于干净的状态:

kubectl get all

这向我们展示了一个 Kubernetes 服务,如图 5.14所示:

执行 kubectl get all 命令会生成一个输出,显示目前只有一个 Kubernetes 服务正在运行。

图 5.14:目前您应该只运行 Kubernetes 服务

让我们还确保两个节点都在运行并且处于“就绪”状态:

kubectl get nodes

这应该显示我们两个节点都处于“就绪”状态,如图 5.15所示:

现在您应该看到两个节点的状态都是 Ready。

图 5.15:您的集群中应该有两个可用节点

在前面的示例中,在处理节点故障部分,我们看到如果 Pod 重新启动,存储在redis-master中的消息将丢失。原因是redis-master将所有数据存储在其容器中,每当重新启动时,它都会使用不带数据的干净镜像。为了在重新启动后保留数据,数据必须存储在外部。Kubernetes 使用 PVC 来抽象底层存储提供程序,以提供这种外部存储。

要开始此示例,我们将设置 WordPress 安装。

开始 WordPress 安装

让我们从安装 WordPress 开始。我们将演示其工作原理,然后验证在重新启动后存储是否仍然存在:

使用以下命令开始重新安装:

helm install wp stable/wordpress 

这将花费几分钟的时间来处理。您可以通过执行以下命令来跟踪此安装的状态:

kubectl get pods -w

几分钟后,这应该显示我们的 Pod 状态为Running,并且两个 Pod 的就绪状态为1/1,如图 5.16所示:

使用 kubectl get pods -w 命令,您将看到 Pod 从 ContainerCreating 转换为 Running 状态,并且您将看到 Ready pods 的数量从 0/1 变为 1/1。

图 5.16:几分钟后,所有 Pod 都将显示为运行状态

在本节中,我们看到了如何安装 WordPress。现在,我们将看到如何使用持久卷来避免数据丢失。

使用持久卷来避免数据丢失

持久卷PV)是在 Kubernetes 集群中存储持久数据的方法。我们在第三章在 AKS 上部署应用程序中更详细地解释了 PV。让我们探索为 WordPress 部署创建的 PV:

  1. 在我们的情况下,运行以下describe nodes命令:
kubectl describe nodes

滚动查看输出,直到看到类似于图 5.17的部分。在我们的情况下,两个 WordPress Pod 都在节点 0 上运行:

当您执行 kubectl describe nodes 命令时,您将看到指示两个 Pod 正在节点 0 上运行的信息。

图 5.17:在我们的情况下,两个 WordPress Pod 都在节点 0 上运行

您的 Pod 放置可能会有所不同。

  1. 我们可以检查的下一件事是我们的 PVC 的状态:
kubectl get pvc

这将生成一个如图 5.18所示的输出:

输出显示了两个 PVC。除了它们的名称,您还可以看到这些 PVC 的状态、卷、容量和访问模式。

图 5.18:WordPress 部署创建了两个 PVC

以下命令显示了绑定到 Pod 的实际 PV:

kubectl describe pv

这将向您显示两个卷的详细信息。我们将在图 5.19中向您展示其中一个:

使用 kubectl describe pvc 命令,您可以详细查看两个 PVC。图片突出显示了默认/data-wp-mariadb-0 的声明,并突出显示了 Azure 中的 diskURI。使用 kubectl describe pvc 命令,您可以详细查看两个 PVC。图片突出显示了默认/data-wp-mariadb-0 的声明,并突出显示了 Azure 中的 diskURI。

图 5.19:一个 PVC 的详细信息

在这里,我们可以看到哪个 Pod 声明了这个卷,以及 Azure 中的DiskURI是什么。

  1. 验证您的网站是否实际在运行:
kubectl get service

这将显示我们的 WordPress 网站的公共 IP,如图 5.20所示:

输出屏幕仅显示了 wp-WordPress 服务的 External-IP。

图 5.20:获取服务的公共 IP
  1. 如果您还记得第三章AKS 的应用部署,Helm 向我们显示了获取 WordPress 网站管理员凭据所需的命令。让我们获取这些命令并执行它们以登录到网站,如下所示:
helm status wp
echo Username: user
echo Password: $(kubectl get secret --namespace default wp-wordpress -o jsonpath="{.data.wordpress-password}" | base64 -d)

这将向您显示用户名密码,如图 5.21所示:

输出显示如何通过 Helm 获取用户名和密码。截图中的用户名是 user,密码是 lcsUSJTk8e。在您的情况下,密码将不同。

图 5.21:获取 WordPress 应用程序的用户名和密码

注意

您可能会注意到,我们在书中使用的命令与helm返回的命令略有不同。helm为密码返回的命令对我们不起作用,我们为您提供了有效的命令。

我们可以通过以下地址登录到我们的网站:http://<external-ip>/admin。在这里使用前一步骤中的凭据登录。然后,您可以继续添加一篇文章到您的网站。点击撰写您的第一篇博客文章按钮,然后创建一篇简短的文章,如图 5.22所示:

您将看到一个欢迎您来到 WordPress 的仪表板。在这里,您将看到一个按钮,上面写着-写下您的第一篇博客文章。单击它开始写作。

图 5.22:撰写您的第一篇博客文章

现在输入一些文本,然后单击发布按钮,就像图 5.23中所示。文本本身并不重要;我们写这个来验证数据确实被保留到磁盘上:

假设您随机输入了单词“测试”。在写完文字后,单击屏幕右上角的“发布”按钮。

图 5.23:发布包含随机文本的文章

如果您现在转到您网站的主页http://<external-ip>,您将会看到您的测试文章,就像图 5.23中所示。我们将在下一节验证此文章是否经得起重启。

处理涉及 PVC 的 Pod 故障

我们将对我们的 PVC 进行的第一个测试是杀死 Pod 并验证数据是否确实被保留。为此,让我们做两件事:

  1. 观察我们应用程序中的 Pod。为此,我们将使用当前的 Cloud Shell 并执行以下命令:
kubectl get pods -w
  1. 杀死已挂载 PVC 的两个 Pod。为此,我们将通过单击工具栏上显示的图标创建一个新的 Cloud Shell 窗口,如图 5.24所示:单击工具栏左侧花括号图标旁边的图标,单击以打开新的 Cloud Shell。
图 24:打开新的 Cloud Shell 实例

一旦您打开一个新的 Cloud Shell,执行以下命令:

kubectl delete pod --all

如果您使用watch命令,您应该会看到类似于图 5.25所示的输出:

运行 kubectl get pods -w 会显示旧的 Pod 被终止并创建新的 Pod。新的 Pod 会从 Pending 状态过渡到 ContainerCreating 再到 Running。

图 5.25:删除 Pod 后,Kubernetes 将自动重新创建两个 Pod

正如您所看到的,Kubernetes 迅速开始创建新的 Pod 来从 Pod 故障中恢复。这些 Pod 经历了与原始 Pod 相似的生命周期,从PendingContainerCreating再到Running

  1. 如果您转到您的网站,您应该会看到您的演示文章已经被保留。这就是 PVC 如何帮助您防止数据丢失的方式,因为它们保留了容器本身无法保留的数据。

图 5.26显示,即使 Pod 被重新创建,博客文章仍然被保留:

您可以看到您的数据被持久保存,带有“test”字样的博客帖子仍然可用。

图 5.26:您的数据被持久保存,您的博客帖子仍然存在

在这里观察的最后一个有趣的数据点是 Kubernetes 事件流。如果运行以下命令,您可以看到与卷相关的事件:

kubectl get events | grep -i volume

这将生成如图 5.27所示的输出。

输出屏幕将显示一个 FailedAttachVolume 警告。在其下方,它将显示状态现在正常,并带有 SuccessfulAttachVolume 消息。这表明 Kubernetes 最初无法挂载卷,但在下一次尝试时成功挂载了它。

图 5.27:Kubernetes 处理了 FailedAttachVolume 错误

这显示了与卷相关的事件。有两条有趣的消息需要详细解释:FailedAttachVolumeSuccessfulAttachVolume。这向我们展示了 Kubernetes 如何处理具有 read-write-once 配置的卷。由于特性是只能从单个 Pod 中读取和写入,Kubernetes 只会在成功从当前 Pod 卸载卷后,将卷挂载到新的 Pod 上。因此,最初,当新的 Pod 被调度时,它显示了FailedAttachVolume消息,因为卷仍然附加到正在删除的 Pod 上。之后,Pod 成功挂载了卷,并通过SuccessfulAttachVolume消息显示了这一点。

在本节中,我们已经学习了 PVC 在 Pod 在同一节点上重新创建时可以起到的作用。在下一节中,我们将看到当节点发生故障时 PVC 的使用情况。

使用 PVC 处理节点故障

在前面的示例中,我们看到了 Kubernetes 如何处理具有 PV 附加的 Pod 故障。在这个示例中,我们将看看 Kubernetes 在卷附加时如何处理节点故障:

  1. 让我们首先检查哪个节点托管了我们的应用程序,使用以下命令:
kubectl get pods -o wide

我们发现,在我们的集群中,节点 1 托管了 MariaDB,节点 0 托管了 WordPress 网站,如图 5.28所示:

运行 kubectl get pods -o wide 会显示哪个 pod 在哪个节点上运行。在截图中,一个 pod 在每个主机上运行。

图 5.28:我们的部署中有两个正在运行的 Pod - 一个在节点 1 上,一个在节点 0 上
  1. 我们将引入一个故障,并停止可能会造成最严重损害的节点,即关闭 Azure 门户上的节点 0。我们将以与先前示例相同的方式进行。首先,查找支持我们集群的规模集,如图 5.29所示:在 Azure 门户的搜索栏中键入 vmss 将显示托管您 AKS 集群的 VMSS 的完整名称。
图 5.29:查找支持我们集群的规模集
  1. 然后按照图 5.30中所示关闭节点:要关闭节点,请在 Azure 门户中导航窗格中的“实例”选项卡上单击。您将看到两个节点。选择第一个节点,然后单击工具栏上的“停用”按钮。
图 5.30:关闭节点
  1. 完成此操作后,我们将再次观察我们的 Pod,以了解集群中正在发生的情况:
kubectl get pods -o wide -w

与先前的示例一样,Kubernetes 将在 5 分钟后开始采取行动来应对我们失败的节点。我们可以在图 5.31中看到这一情况:

当您执行 kubectl get pods -o wide -w 命令时,您将看到状态为 Pending 的 Pod 未分配到节点。

图 5.31:处于挂起状态的 Pod
  1. 我们在这里遇到了一个新问题。我们的新 Pod 处于“挂起”状态,尚未分配到新节点。让我们弄清楚这里发生了什么。首先,我们将“描述”我们的 Pod:
kubectl describe pods/wp-wordpress-<pod-id>

您将得到一个如图 5.32所示的输出:

您将看到此 Pod 处于挂起状态的原因的详细信息,这是由于 CPU 不足造成的。

图 5.32:显示处于挂起状态的 Pod 的输出
  1. 这表明我们的集群中没有足够的 CPU 资源来托管新的 Pod。我们可以使用kubectl edit deploy/...命令来修复任何不足的 CPU/内存错误。我们将将 CPU 请求从 300 更改为 3,以便我们的示例继续进行:
kubectl edit deploy wp-wordpress

这将带我们进入一个vi环境。我们可以通过输入以下内容快速找到与 CPU 相关的部分:

/cpu <enter>

一旦到达那里,将光标移动到两个零上,然后按两次x键删除零。最后,键入:wq!以保存我们的更改,以便我们可以继续我们的示例。

  1. 这将导致创建一个新的 ReplicaSet 和一个新的 Pod。我们可以通过输入以下命令来获取新 Pod 的名称:
kubectl get pods 

查找状态为ContainerCreating的 Pod,如下所示:

输出屏幕显示了四个具有不同状态的 Pod。查找具有 ContainerCreating 状态的 Pod。

图 5.33:新的 Pod 被卡在 ContainerCreating 状态
  1. 让我们用describe命令查看该 Pod 的详细信息:
kubectl describe pod wp-wordpress-<pod-id>

在这个describe输出的“事件”部分,您可以看到以下错误消息:

在具有 ContainerCreating 状态的 Pod 上执行 kubectl describe 命令会显示一个详细的错误消息,其中包含 FailedMount 的原因。消息表示 Kubernetes 无法挂载卷。

图 5.34:新的 Pod 有一个新的错误消息,描述了卷挂载问题
  1. 这告诉我们,我们的新 Pod 想要挂载的卷仍然挂载到了被卡在“终止”状态的 Pod 上。我们可以通过手动从我们关闭的节点上分离磁盘并强制删除被卡在“终止”状态的 Pod 来解决这个问题。

注意

处于“终止”状态的 Pod 的行为不是一个错误。这是默认的 Kubernetes 行为。Kubernetes 文档中指出:“Kubernetes(1.5 版本或更新版本)不会仅仅因为节点不可达而删除 Pods。在不可达节点上运行的 Pods 在超时后进入“终止”或“未知”状态。当用户尝试在不可达节点上优雅地删除 Pod 时,Pods 也可能进入这些状态。”您可以在kubernetes.io/docs/tasks/run-application/force-delete-stateful-set-pod/阅读更多内容。

  1. 为此,我们需要规模集的名称和资源组的名称。要找到这些信息,请在门户中查找规模集,如图 5.35所示:在 Azure 搜索栏中键入 vmss 以查找支持您集群的 ScaleSet
图 5.35:查找支持您集群的规模集
  1. 在规模集视图中,复制并粘贴规模集名称和资源组。编辑以下命令以从失败的节点分离磁盘,然后在 Cloud Shell 中运行此命令:
az vmss disk detach --lun 0 --vmss-name <vmss-name> -g <rgname> --instance-id 0
  1. 这将从节点 0 中分离磁盘。这里需要的第二步是在 Pod 被卡在终止状态时,强制将其从集群中移除:
kubectl delete pod wordpress-wp-<pod-id> --grace-period=0 --force
  1. 这将使我们的新 Pod 恢复到健康状态。系统需要几分钟来接受更改,然后挂载和调度新的 Pod。让我们再次使用以下命令获取 Pod 的详细信息:
kubectl describe pod wp-wordpress-<pod-id>

这将生成以下输出:

输出将显示两个 Pod 的事件类型为 Normal。原因是 SuccessfulAttachVolume 和 Pulled 的第二个 Pod。

图 5.36:我们的新 Pod 现在正在挂载卷并拉取容器镜像
  1. 这表明新的 Pod 成功挂载了卷,并且容器镜像已被拉取。让我们验证一下 Pod 是否真的在运行:
kubectl get pods

这将显示 Pod 正在运行,如图 5.37所示:

两个 Pod 的状态为 Running。

图 5.37:两个 Pod 都成功运行

这将使您的 WordPress 网站再次可用。

在继续之前,让我们使用以下命令清理我们的部署:

helm delete wp
kubectl delete pvc --all
kubectl delete pv --all

让我们还重新启动关闭的节点,如图 5.38所示:

要重新启动关闭的节点,请单击导航窗格中的实例选项卡。选择已分配/已停止的节点。单击搜索栏旁边的工具栏中的启动按钮。您的节点现在应该已重新启动。

图 5.38:重新启动节点 1

在本节中,我们介绍了当 PVC 未挂载到新的 Pod 时,您如何从节点故障中恢复。我们需要手动卸载磁盘,然后强制删除处于Terminating状态的 Pod。

总结

在本章中,您了解了常见的 Kubernetes 故障模式以及如何从中恢复。我们从一个示例开始,介绍了 Kubernetes 如何自动检测节点故障并启动新的 Pod 来恢复工作负载。之后,您扩展了工作负载,导致集群资源耗尽。您通过重新启动故障节点来为集群添加新资源,从这种情况中恢复了过来。

接下来,您看到了 PV 是如何有用地将数据存储在 Pod 之外的。您关闭了集群上的所有 Pod,并看到 PV 确保应用程序中没有数据丢失。在本章的最后一个示例中,您看到了当 PV 被附加时,您如何从节点故障中恢复。您通过从节点卸载磁盘并强制删除终止的 Pod 来恢复工作负载。这将使您的工作负载恢复到健康状态。

本章已经解释了 Kubernetes 中常见的故障模式。在下一章中,我们将为我们的服务引入 HTTPS 支持,并介绍与 Azure 活动目录的身份验证。

第六章:使用 HTTPS 和 Azure AD 保护您的应用程序

HTTPS 已经成为任何面向公众的网站的必需品。它不仅提高了网站的安全性,而且还成为新的浏览器功能的要求。HTTPS 是 HTTP 协议的安全版本。HTTPS 利用传输层安全TLS)证书来加密终端用户和服务器之间的流量,或者两个服务器之间的流量。TLS 是安全套接字层SSL)的后继者。术语TLSSSL经常可以互换使用。

过去,您需要从证书颁发机构CA)购买证书,然后在您的 Web 服务器上设置它们,并定期更新它们。虽然今天仍然可能,但 Let's Encrypt 服务和 Kubernetes 中的助手使在集群中设置经过验证的 TLS 证书变得非常容易。Let's Encrypt 是由互联网安全研究组织运营并得到多家公司支持的非营利组织。它是一个提供自动验证 TLS 证书的免费服务。自动化是 Let's Encrypt 服务的一个关键优势。

在 Kubernetes 助手方面,我们将介绍一个名为Ingress的新对象,并且我们将使用一个名为cert-manager的 Kubernetes 附加组件。Ingress 是 Kubernetes 中管理对服务的外部访问的对象。Ingress 通常用于 HTTP 服务。Ingress 在我们在第三章,AKS 上的应用部署中解释的服务对象之上添加了额外的功能。Ingress 可以配置为处理 HTTPS 流量。它还可以根据由用于连接的域名系统DNS)分配的主机名配置为将流量路由到不同的后端服务。

cert-manager是一个 Kubernetes 附加组件,可帮助自动创建 TLS 证书。它还可以在证书临近到期时帮助进行轮换。cert-manager可以与 Let's Encrypt 接口自动请求证书。

在本章中,我们将看到如何设置 Ingress 和cert-manager与 Let's Encrypt 接口。

此外,在本章中,我们将探讨为 guestbook 应用程序进行身份验证的不同方法。我们将使用oauth2_proxy反向代理来向示例 guest 应用程序添加 Azure Active Directory(AD)的身份验证。oauth2_proxy是一个反向代理,将身份验证请求转发到配置身份验证平台。您将学习如何轻松地保护没有内置身份验证的应用程序。身份验证方案可以扩展为使用 GitHub、Google、GitLab、LinkedIn 或 Facebook。

本章将涵盖以下主题:

  • 在服务前设置 Ingress

  • 为 Ingress 添加 TLS 支持

  • 身份验证和常见身份验证提供程序

  • 身份验证与授权

  • 部署oauth2_proxy sidecar

让我们从设置 Ingress 开始。

HTTPS 支持

传统上,获取 TLS 证书一直是一项昂贵且繁琐的业务。如果您想以低成本完成,可以自行签署证书,但浏览器在打开您的网站时会抱怨并将其标识为不受信任。Let's Encrypt 改变了这一切。Let's Encrypt 是一个免费、自动化和开放的 CA,为公众利益而运行。它为人们提供数字证书,以便免费以最用户友好的方式为网站启用 HTTPS(SSL/TLS)。

注意

尽管本节侧重于使用诸如 Let's Encrypt 之类的自动化服务,您仍然可以选择传统的方式从现有 CA 购买证书并将其导入 Kubernetes。

安装 Ingress 控制器

通过 Ingress 对象,Kubernetes 提供了一种安全地公开您的服务的清晰方式。它提供了 SSL 终端点和基于名称的路由,这意味着不同的 DNS 名称可以路由到不同的后端服务。

如果您想在集群中创建 Ingress 对象,首先需要设置 Ingress 控制器。Ingress 控制器将管理您在集群中部署的 Ingresses 的状态。在选择 Ingress 控制器时有多个选项。有关所有选项的完整列表,请参阅kubernetes.io/docs/concepts/services-networking/ingress-controllers/。在运行 AKS 时,最常见的两个选项要么使用基于 NGINX 的 Ingress 控制器,要么使用基于 Azure 应用程序网关的 Ingress 控制器。在我们的示例中,我们将使用 NGINX 版本。

让我们继续安装 Ingress 控制器的 NGINX 版本,执行以下步骤:

  1. 要跟着操作,请在 Cloud Shell 的 Bash 版本中运行此示例。

  2. 输入以下命令开始安装:

helm repo add stable https://kubernetes-charts.storage.googleapis.com/ 
helm install ingress stable/nginx-ingress

这将为我们的集群设置 Ingress 控制器。这还将创建一个我们将用于访问 Ingress 控制器的公共 IP。

  1. 让我们连接到 Ingress 控制器。要获取ingress-controller服务的公开 IP,请输入此命令:
kubectl get service 

您应该看到 Ingress 控制器的条目,如图 6.1所示:

您的输出屏幕将显示三个条目。在这三个条目中,Ingress 控制器将显示一个外部 IP。

图 6.1:获取 Ingress 控制器的 IP

您可以在浏览器中输入http://<EXTERNAL-IP>来浏览网页:

您的输出屏幕将显示三个条目。在这三个条目中,Ingress 控制器将显示一个外部 IP。

图 6.2:显示其默认后端的 Ingress

这向您展示了两件事:

  1. 没有后端应用程序,只有一个默认应用程序。

  2. 网站通过 HTTP 提供服务,而不是 HTTPS(因此会出现不安全警告)。

在接下来的两个部分中,我们将解决这两个问题。我们将首先为我们的留言板应用程序创建一个 Ingress 规则,然后我们将通过 Let's Encrypt 添加 HTTPS 支持。

为留言板应用程序添加 Ingress 规则

让我们从重新启动我们的留言板应用程序开始。要启动留言板应用程序,请输入以下命令:

kubectl create -f guestbook-all-in-one.yaml

这将创建我们信任的留言板应用程序。您应该看到对象被创建,如图 6.3所示:

执行 kubectl create -f guestbook-all-in-one.yaml 命令后,您的输出屏幕将显示所有对象都已创建。

图 6.3:创建留言板应用程序

然后,我们可以使用以下 YAML 文件通过 Ingress 公开前端服务。这在本章的源代码中提供为simple-frontend-ingress.yaml

1   apiVersion: extensions/v1beta1
2   kind: Ingress
3   metadata:
4     name: simple-frontend-ingress
5   spec:
6     rules:
7     - http:
8         paths:
9         - path: /
10          backend:
11            serviceName: frontend
12            servicePort: 80

让我们看看我们在这个 YAML 文件中定义了什么:

  • 第 2 行:在这里,我们定义了我们正在创建一个 Ingress 对象的事实。

  • 第 5-12 行:这些行定义了 Ingress 的配置。特别注意:

  • 第 9 行:在这里,我们定义了此 Ingress 正在侦听的路径。在我们的情况下,这是顶级路径。在更高级的情况下,您可以有不同的路径指向不同的服务。

  • 第 10-12 行:这些行定义了应将此流量指向的实际服务。

我们可以使用以下命令创建此 Ingress:

kubectl apply -f simple-frontend-ingress.yaml 

如果您现在转到https://<EXTERNAL-IP>/,您应该会得到如图 6.4所示的输出:

当您在浏览器中输入公共 IP 地址时,它将显示一个带有 Guestbook 字样的白屏。这表明通过 Ingress 可以访问 guestbook 应用程序。

图 6.4:通过 Ingress 访问 guestbook 应用程序

请注意以下内容:我们不必像在前面的章节中那样公开暴露前端服务。我们已将 Ingress 添加为公开服务,前端服务仍然对集群保密:

用户可以访问 Ingress,而服务和前端 pod 保持私密。

图 6.5:显示可公开访问的 Ingress 的流程图

您可以通过运行以下命令来验证这一点:

kubectl get svc

这应该只显示一个公共服务:

运行 kubectl get svc 命令后,您将看到共有六个服务,只有 Ingress 控制器提供了外部 IP。

图 6.6:仅显示具有公共 IP 的 Ingress 的输出

在本节中,您已经启动了一个 guestbook 应用程序的实例。您通过创建 Ingress 将其公开。只有 Ingress 是公开访问的。在下一节中,我们将看到如何从 Let's Encrypt 获取证书。

从 Let's Encrypt 获取证书

在本节中,我们将为我们的应用程序添加 HTTPS 支持。为此,我们需要一个 TLS 证书。我们将使用cert-manager Kubernetes 附加组件从 Let's Encrypt 请求证书。涉及几个步骤。将 HTTPS 添加到我们的应用程序的过程涉及以下步骤:

  1. 安装cert-manager,它与 Let's Encrypt API 接口,以请求您指定的域名的证书。

  2. Azure 完全合格的域名FQDN)映射到 NGINX Ingress 公共 IP。 FQDN 是服务的完整 DNS 名称,有时称为 DNS 记录,例如www.google.com。 为 FQDN 颁发 TLS 证书,这就是为什么我们需要为我们的 Ingress 映射一个 FQDN。

  3. 安装证书颁发者,该颁发者将从 Let's Encrypt 获取证书。

  4. 为给定的 FQDN 创建 SSL 证书。

  5. 通过创建 Ingress 到在步骤 4中创建的证书的服务来保护前端服务部分。在我们的示例中,我们不会执行此步骤。但是,我们将重新配置我们的 Ingress 以自动获取在步骤 4中创建的证书。

让我们从第一步开始;在我们的集群中安装cert-manager

安装 cert-manager

获取 TLS 证书的第一步是在您的集群中安装cert-managercert-manager (github.com/jetstack/cert-manager)自动管理和发放来自各种发放源的 TLS 证书。这是由公司Jetstack管理的开源解决方案。续订证书和确保它们定期更新都由cert-manager管理,这是一个 Kubernetes 附加组件。

以下命令在您的集群中安装cert-manager

kubectl create ns cert-manager
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager --namespace cert-manager jetstack/cert-manager

这些命令在您的集群中执行了一些操作:

  1. 创建一个新的命名空间。命名空间在 Kubernetes 中用于隔离彼此的工作负载。

  2. 向 Helm 添加一个新的存储库以获取图表。

  3. 安装cert-manager Helm 图表。

现在您已经安装了cert-manager,我们可以继续下一步:将 FQDN 映射到 Ingress。

将 Azure FQDN 映射到 NGINX Ingress 公共 IP

获取 TLS 证书过程的下一步是将 FQDN 添加到您的 IP 地址。Let's Encrypt 需要一个公开可用的 DNS 条目来验证 DNS 条目的所有权之前发放证书。这确保您不能劫持别人的站点。我们必须将 Azure 给我们的公共域名映射到我们从 Azure 负载均衡器获取的外部 IP,以证明所有权。

以下步骤将帮助我们将 DNS 条目链接到我们的公共 IP:

  1. 让我们继续将 DNS 名称链接到我们的公共 IP 地址。首先,请确保从您的 Ingress 服务获取 IP 地址:
kubectl get service

记下 Ingress 服务的 IP。在 Azure 搜索栏中,现在搜索public ip

在 Azure 搜索栏中键入公共 IP 并选择“公共 IP 地址”

图 6.7:在 Azure 搜索栏中搜索公共 IP
  1. 一旦到达那里,您应该会看到许多公共 IP 地址。要找到我们的公共 IP 地址,您可以在这里显示一个额外的列,该列将显示实际的 IP 地址。点击“编辑列”按钮添加额外的列:当您从 Azure 门户上的默认目录页面找到您的公共 IP 时,点击页面顶部的“编辑列”按钮。这将打开列选择器。
图 6.8:点击“编辑列”以添加额外的列
  1. 在列选择器中,选择“IP 地址”,然后点击向右的箭头,如图 6.9所示:要将 IP 地址添加到所选列中,请点击“可用列”导航窗格中的 IP 地址选项卡。接下来,点击位于页面中央的向右的箭头。
图 6.9:将 IP 地址添加到所选列
  1. 点击“应用”以显示实际的 IP 地址。当您看到您的 IP 地址时,请点击它。在您的 IP 地址的窗格中,进入“配置”视图。然后,输入一个唯一的 DNS 名称,然后点击“保存”:打开您的 IP 地址。点击导航窗格中的“配置”选项卡。输入 DNS 的唯一名称,然后点击搜索栏旁边的保存按钮。
图 6.10:添加一个唯一的 DNS 名称标签并保存配置

现在您已经有了与您的公共 IP 地址相关联的 DNS 记录。接下来,您将在集群中安装证书颁发机构。

安装证书颁发机构

在本节中,我们将安装 Let's Encrypt 的 staging 证书颁发机构。一个证书可以由多个颁发机构颁发。例如,letsencrypt-staging是用于测试目的。由于我们正在构建测试,我们将使用 staging 服务器。证书颁发机构的代码已经在本章的源代码中提供在certificate-issuer.yaml文件中。像往常一样,使用kubectl create -f certificate-issuer.yaml,其中包含以下内容:

1   apiVersion: cert-manager.io/v1alpha2
2   kind: Issuer
3   metadata:
4     name: letsencrypt-staging
5   spec:
6     acme:
7       server: https://acme-staging-v02.api.letsencrypt.org/directory
8       email: <your e-mailaddress>
9       privateKeySecretRef:
10        name: letsencrypt-staging
11      solvers:
12      - http01:
13          ingress:
14            class: nginx

现在,让我们看看我们在这里定义了什么:

  • 第 1-2 行:在这里,我们使用了我们之前安装的CustomResourceDefinitionCRD)。CRD 是扩展 Kubernetes API 服务器以创建自定义资源的一种方式,比如证书颁发机构。在这种情况下,我们特别指向了我们注入到 Kubernetes API 中的cert-manager API CRD,并创建了一个Issuer对象。

  • 第 6-10 行:我们在这些行中提供了 Let's Encrypt 的配置,并指向了 staging 服务器。

  • 第 11-14 行:这是用于 ACME 客户端认证域名所有权的附加配置。

安装了证书颁发者后,我们现在可以进行下一步:创建 TLS 证书。

创建 TLS 证书并保护我们的服务

在本节中,我们将创建一个 TLS 证书。您可以通过两种方式配置cert-manager来创建证书。您可以手动创建证书并将其链接到 Ingress 控制器,或者您可以配置 Ingress 控制器,以便cert-manager自动创建证书。在本例中,我们将向您展示第二种方法,即通过编辑我们的 Ingress 来使其看起来像以下的 YAML 代码。此文件在 GitHub 上的源代码中存在,名称为ingress-with-tls.yaml

1   apiVersion: extensions/v1beta1
2   kind: Ingress
3   metadata:
4     name: simple-frontend-ingress
5     annotations:
6       cert-manager.io/issuer: "letsencrypt-staging"
7   spec:
8     tls:
9     - hosts:
10      - <your DNS prefix>.<your azure region>.cloudapp.azure.com
11      secretName: frontend-tls
12    rules:
13    - host: <your DNS prefix>.<your Azure location>.cloudapp.azure.com
14      http:
15        paths:
16        - path: /
17          backend:
18            serviceName: frontend
19            servicePort: 80

我们对原始 Ingress 进行了以下更改:

  • 第 6 行:我们已经在 Ingress 上添加了一个指向证书颁发者的注释。

  • 第 10 行和第 13 行:我们在这里添加了 Ingress 的域名。这是必需的,因为 Let's Encrypt 只为域名颁发证书。

  • 第 11 行:这是将用于存储我们的证书的密钥的名称。

您可以使用以下命令更新我们之前创建的 Ingress:

kubectl apply -f ingress-with-tls.yaml

cert-manager大约需要一分钟来请求证书并配置我们的 Ingress 以使用该证书。在等待期间,让我们来看看cert-manager代表我们创建的中间资源。

首先,cert-manager为我们创建了一个certificate对象。我们可以使用以下命令查看该对象的状态:

kubectl get certificate

执行此命令将生成一个输出,如图 6.11所示:

当执行 kubectl get certificate 命令时,您应该看到对象的状态为 False。这表示证书尚未准备就绪。

图 6.11:显示证书状态的输出

如您所见,我们的证书尚未准备就绪。cert-manager创建了另一个对象来实际获取证书。这个对象是certificaterequest。我们可以使用以下命令获取其状态:

kubectl get certificaterequest

这将生成如图 6.12所示的输出:

接下来,当执行 kubectl get certificaterequest 命令时,这也显示对象的状态为 False。这表示证书请求对象尚未准备就绪。

图 6.12:输出显示证书请求对象的状态为 False

我们还可以通过针对certificaterequest对象发出describe命令来获取有关请求的更多详细信息:

kubectl describe certificaterequest

当我们在等待证书签发时,状态将类似于图 6.13

使用 kubectl describe certificaterequest 命令,您可以获取有关 certificaterequest 对象的更多详细信息,指示状态为 False。它显示一条消息说“创建了订单资源 default/frontend-tls-<随机 ID>”。

图 6.13:提供有关 certificaterequest 对象的更多详细信息的输出

如果我们再给它几秒钟,describe命令应该会返回一个成功创建证书的消息,如图 6.14所示:

几秒钟后,您将看到详细信息已更新,状态更改为 True。有一条消息说“成功从签发者获取证书”,这意味着证书现在已签发。

图 6.14:显示已签发证书的输出

现在,这应该可以使我们的前端 Ingress 通过 HTTPS 提供服务。让我们在浏览器中尝试一下,浏览到您在映射 FQDN 部分创建的 DNS 名称。这将在浏览器中显示错误,显示证书无效,如图 6.15所示。这是可以预期的,因为我们正在使用 Let's Encrypt 临时服务:

屏幕将显示一条消息,说明您的连接不是私密的。

图 6.15:使用 Let's Encrypt 临时服务器,我们的证书默认不受信任

您可以通过单击高级并选择继续来浏览到您的应用程序。

由于我们能够完成临时证书的测试,现在我们可以转向生产环境。

从临时切换到生产

在本节中,我们将从临时证书切换到生产级证书。要做到这一点,您可以通过在集群中创建一个新的签发者来重新执行上一个练习,就像以下所示(在certificate-issuer-prod.yaml中提供)。不要忘记在文件中更改您的电子邮件地址:

1   apiVersion: cert-manager.io/v1alpha2
2   kind: Issuer
3   metadata:
4     name: letsencrypt-prod
5   spec:
6     acme:
7       server: https://acme-v02.api.letsencrypt.org/directory
8       email: <your e-mail>
9       privateKeySecretRef:
10        name: letsencrypt-staging
11      solvers:
12      - http01:
13          ingress:
14            class: nginx

然后将ingress-with-tls.yaml文件中对签发者的引用替换为letsencrypt-prod,就像这样(在ingress-with-tls-prod.yaml文件中提供):

1   apiVersion: extensions/v1beta1
2   kind: Ingress
3   metadata:
4     name: simple-frontend-ingress
5     annotations:
6       cert-manager.io/issuer: "letsencrypt-prod"
7   spec:
8     tls:
9     - hosts:
10      - <your dns prefix>.<your azure region>.cloudapp.azure.com
11      secretName: frontend-tls
12    rules:
13    - host: <your dns prefix>.<your azure region>.cloudapp.azure.com
14      http:
15        paths:
16        - path: /
17          backend:
18            serviceName: frontend
19            servicePort: 80

要应用这些更改,请执行以下命令:

kubectl create -f certificate-issuer-prod.yaml
kubectl apply -f ingress-with-tls-prod.yaml

证书再次生效大约需要一分钟。一旦新证书发放,您可以再次浏览到您的 DNS 名称,并且不应该再看到关于无效证书的警告。如果您单击浏览器中的挂锁项目,您应该会看到您的连接是安全的,并使用有效的证书。

单击浏览器地址栏上的挂锁图标将告诉您连接是安全的。这个弹出窗口还会让您知道证书是有效的。

图 6.16:显示有效证书的网页

在本节中,我们介绍了两个新概念:首先,我们为我们的留言板应用程序添加了一个 Ingress。我们首先在集群上设置了一个 Ingress 控制器,然后为我们的留言板应用程序设置了 Ingress。Ingress 允许在 Kubernetes 之上为应用程序进行高级路由和 HTTPS 卸载。之后,我们为我们的留言板应用程序添加了 HTTPS。我们不需要更改应用程序本身的源代码;通过配置我们的 Ingress,我们能够添加 HTTPS 支持。

在接下来的部分,我们将为我们的应用程序添加另一个安全层。我们将为我们的应用程序添加认证。在深入讨论之前,让我们首先讨论一下关于认证和授权的常见误解。

认证与授权

认证AuthN)经常与授权AuthZ)混淆。认证涉及身份(你是谁?),通常需要一个受信任的身份提供者。存在多个提供者,如 Azure AD、Okta 或 GitHub,甚至社交媒体平台如 Facebook、Google 或 Twitter 也可以用作提供者。授权涉及权限(你想做什么?),在应用程序资源需要受保护方面非常具体。

通常需要多次尝试才能理解两者之间的区别,即使这样,你仍然可能会在两者之间感到困惑。混淆的根源在于,在某些情况下,认证提供者和授权提供者是相同的。例如,在我们的 WordPress 示例中,WordPress 提供认证(它有用户名和密码)和授权(它将用户存储在管理员或用户角色下,例如)。

然而,在大多数情况下,身份验证系统和授权系统是不同的。我们将在第十章保护您的 AKS 集群中使用一个实际的例子。在该章节中,我们将使用 Azure AD 作为身份验证源,同时使用 Kubernetes RBAC 作为授权源。

身份验证和常见的身份验证提供商

我们的留言板应用对所有人开放,并允许任何拥有公共 IP 的人访问该服务。图像本身没有身份验证。一个常见的问题是希望将附加功能与应用程序实现分开。这可以通过引入一个代理来实现,该代理将提供身份验证流量,而不是在主应用程序中引入身份验证逻辑。

最近的黑客攻击表明,自己建立和维护安全的身份验证系统是困难的。为了帮助客户构建安全的应用程序,许多公司允许您使用他们的身份验证服务来验证用户的身份。这些提供商提供了 OAuth 支持的身份验证服务。以下是一些知名的提供商:

在接下来的章节中,我们将使用代理实现oauth2_proxy来为我们的留言板示例实现身份验证。

部署 oauth2_proxy 代理

让我们从清理之前部署的 Ingress 开始。我们将保留集群中部署的证书颁发机构。我们可以使用以下方式清理 Ingress:

kubectl delete -f ingress-with-tls-prod.yaml

我们将实现来自 Pusher 的oauth2_proxy (github.com/pusher/oauth2_proxy)。按照以下步骤配置oauth2_proxy以使用 Azure AD 作为身份验证系统。

首先,在 Azure AD 中注册一个应用程序。通过在搜索栏中搜索azure active directory来打开门户中的 Azure AD 选项卡:

在搜索栏中输入 Azure 活动目录。

图 6.17:在 Azure 搜索栏中搜索 Azure 活动目录

然后,转到应用注册并点击新注册

在 Azure 活动目录的导航窗格中点击应用注册。接下来,点击搜索栏旁边的新注册选项卡。将创建一个新的应用程序注册。

图 6.18:创建新的应用程序注册

然后,为应用程序提供名称并点击创建

为新创建的应用程序输入名称并保存。

图 6.19:为应用程序提供名称

接下来,通过以下步骤创建客户端 ID 密钥:

  1. 选择证书和密钥,然后转到新的客户端密钥。为密钥提供描述并点击添加打开应用程序后,点击左侧屏幕导航窗格中的证书和密钥选项卡。点击新的客户端密钥按钮。将 oauth2_proxy 设置为此应用程序的描述。将到期时间设置为 1 年,然后点击添加按钮。
图 6.20:创建新的客户端密钥
  1. 点击复制图标并将密钥保存在安全的地方:要复制并将密钥保存在安全的地方,请点击客户端密钥值旁边的复制图标。
图 6.21:复制客户端密钥
  1. 接下来,我们需要配置重定向 URL。这是 Azure AD 在用户经过身份验证后将回调的 URL。要进行配置,请转到 Azure AD 的身份验证,点击添加平台,然后选择Web,如图 6.22所示:点击左侧屏幕导航窗格中的身份验证选项卡。接下来,点击平台配置部分中的添加平台按钮。这将打开一个新页面,帮助您配置平台。在 Web 应用程序部分中选择 Web 选项。
图 6.22:提供重定向 URL

在那里,您可以输入以下 URL:

https://<your dns prefix>.<your azure region>.cloudapp.azure.com/oauth2/callback,然后点击配置

  1. 然后,返回概述窗格并保存应用程序目录 ID在左侧屏幕的概述窗格中复制应用程序 ID 和目录 ID 并保存。
图 6.23:复制应用程序 ID 和目录 ID

在创建客户端 ID 密钥、设置重定向 URL 和复制应用程序和目录 ID 之后,我们需要在 Kubernetes 中创建以下三个对象,以使oauth2_proxy在我们的集群上运行,并执行最后一步将 OAuth 链接到我们现有的 Ingress:

  1. 首先,我们需要为oauth2_proxy创建一个部署。

  2. 然后,我们需要将其公开为一个服务。

  3. 之后,我们将为oauth2创建一个新的 Ingress。

  4. 最后,我们将重新配置当前的 Ingress,以便将未经身份验证的请求发送到oauth2_proxy

我们将执行所有三个步骤,并显示 YAML 文件如下:

  1. 让我们从第一项开始 - 创建部署。 部署可以在源代码中找到oauth2_deployment.yaml文件:
1    apiVersion: extensions/v1beta1
2    kind: Deployment
3    metadata:
4      name: oauth2-proxy
5    spec:
6      replicas: 1
7      selector:
8        matchLabels:
9          app: oauth2-proxy
10     template:
11       metadata:
12         labels:
13           app: oauth2-proxy
14       spec:
15         containers:
16         - env:
17             - name: OAUTH2_PROXY_PROVIDER
18               value: azure
19             - name: OAUTH2_PROXY_AZURE_TENANT
20               value: <paste in directory ID>
21             - name: OAUTH2_PROXY_CLIENT_ID
22               value: <paste in application ID>
23             - name: OAUTH2_PROXY_CLIENT_SECRET
24               value: <paste in client secret>
25             - name: OAUTH2_PROXY_COOKIE_SECRET
26               value: somethingveryrandom
27             - name: OAUTH2_PROXY_HTTP_ADDRESS
28               value: "0.0.0.0:4180"
29             - name: OAUTH2_PROXY_UPSTREAM
30               value: "https://<your DNS prefix>.<your azure region>.cloudapp.azure.com/"
31             - name: OAUTH2_PROXY_EMAIL_DOMAINS
32               value: '*'
33           image: quay.io/pusher/oauth2_proxy:latest
34           imagePullPolicy: IfNotPresent
35           name: oauth2-proxy
36           ports:
37           - containerPort: 4180
38             protocol: TCP

这个部署有几行有趣的内容需要讨论。 我们在第三章的先前示例中讨论了其他行。 我们将在这里重点讨论以下行:

第 17-18 行:这些行告诉oauth2代理重定向到 Azure AD。

第 19-32 行:这是oauth2的配置。

第 33 行:这行指向正确的容器镜像。 这是我们第一次使用不是托管在 Docker Hub 上的镜像。 Quay是由 RedHat 托管的另一个容器存储库。

使用以下命令创建部署:

kubectl create -f oauth2_deployment.yaml
  1. 接下来,oauth2需要被公开为一个服务,以便 Ingress 可以与其通信,通过创建以下服务(oauth2_service.yaml):
1   apiVersion: v1
2   kind: Service
3   metadata:
4     name: oauth2-proxy
5     namespace: default
6   spec:
7     ports:
8     - name: http
9       port: 4180
10      protocol: TCP
11      targetPort: 4180
12    selector:
13      app: oauth2-proxy

使用以下命令创建此服务:

kubectl create oauth2_service.yaml
  1. 接下来,我们将创建一个 Ingress,以便任何访问handsonaks-ingress-<yourname>.<your azure region>.cloudapp.azure.com/oauth的 URL 将被重定向到oauth2-proxy服务。 这里使用相同的 Let's Encrypt 证书颁发者(本章的源代码中的oauth2_ingress.yaml文件):
1   apiVersion: extensions/v1beta1
2   kind: Ingress
3   metadata:
4     name: oauth2-proxy-ingress
5     annotations:
6       kubernetes.io/ingress.class: nginx
7       cert-manager.io/issuer: "letsencrypt-prod"
8   spec:
9     tls:
10     - hosts:
11       - <your DNS prefix>.<your azure region>.cloudapp.azure.com
12       secretName: tls-secret
13     rules:
14     - host: <your DNS prefix>.<your azure region>.cloudapp.azure.com
15       http:
16         paths:
17         - path: /oauth2
18           backend:
19             serviceName: oauth2-proxy
20             servicePort: 4180

在这个 Ingress 中有一行很有趣。 第 17 行引入了一个新的路径到我们的 Ingress。 正如本章前面提到的,同一个 Ingress 可以将多个路径指向不同的后端服务。 这就是我们在这里配置的内容。

使用以下命令创建此 Ingress:

kubectl create -f oauth2_ingress.yaml
  1. 最后,我们将通过创建 Ingress 将oauth2代理链接到前端服务,以配置nginx,以便使用auth-urlauth-signin中的路径进行身份验证检查。如果请求未经身份验证,则流量将发送到oauth2_proxy。如果成功经过身份验证,则流量将重定向到后端服务(在我们的案例中是前端服务)。

在 GitHub 存储库中的以下代码执行身份验证成功后的重定向(frontend-oauth2-ingress.yaml):

1  apiVersion: extensions/v1beta1
2  kind: Ingress
3  metadata:
4    name: frontend-oauth2-ingress
5    annotations:
6      kubernetes.io/ingress.class: nginx
7      nginx.ingress.kubernetes.io/auth-url: "http://oauth2-proxy.default.svc.cluster.local:4180/oauth2/auth"
8      nginx.ingress.kubernetes.io/auth-signin: "http://<your DNS prefix>.<your azure region>.cloudapp.azure.com/oauth2/start"
9  spec:
10   rules:
11   - host: <your DNS prefix>.<your azure region>.cloudapp.azure.com
12     http:
13       paths:
14       - path: /
15         backend:
16           serviceName: frontend
17           servicePort: 80 

在这个 Ingress 配置中有一些有趣的地方要指出。其他行与我们在本章中创建的其他 Ingress 一样普通:

第 5 行:如前所述,Ingress 对象可以由多种技术(如 NGINX 或应用程序网关)支持。 Ingress 对象具有配置基本任务的语法,例如hostspaths,但例如没有配置身份验证重定向。注释由多个 Ingress 提供程序使用,以将详细配置数据传递给后端的 Ingress 提供程序。

第 7-8 行:这将配置我们的 Ingress 将非经过身份验证的请求发送到这些 URL。

使用以下命令创建此 Ingress:

kubectl create -f frontend-oauth2-ingress.yaml

我们现在已经完成了配置。您现在可以使用现有的 Microsoft 帐户登录到https://handsonaks-ingress-<yourname>.<your azure region>.cloudapp.azure.net/的服务。为了确保您获得身份验证重定向,请确保使用新的浏览器窗口或私人窗口。您应该会自动重定向到 Azure AD 的登录页面。

注意

oauth2-proxy支持多个身份验证提供程序,例如 GitHub 和 Google。只需更改oauth2-proxy部署的 YAML 以使用正确的服务更改身份验证提供程序。请参阅 https://github.com/pusher/oauth2_proxy#oauth-provider-configuration 中的相关详细信息。

现在一切都已部署完成,让我们清理一下我们在集群中创建的资源:

kubectl delete -f guestbook-all-in-one.yaml
kubectl delete -f frontend-oauth2-ingress.yaml
kubectl delete -f oauth2_ingress.yaml
kubectl delete -f oauth2_deployment.yaml
kubectl delete -f oauth2_service.yaml
kubectl delete ns cert-manager
helm delete ingress

在本节中,我们已将 Azure AD 身份验证添加到您的应用程序。我们通过将oauth2_proxy添加到我们的集群,然后重新配置现有的 Ingress 以将未经身份验证的请求重定向到oauth2_proxy来实现这一点。

摘要

在本章中,我们在不实际更改源代码的情况下,为我们的留言板应用程序添加了 HTTPS 安全性和身份控制。我们首先在集群中设置了 Kubernetes Ingress 对象。然后,我们安装了一个证书管理器,该管理器与 Let's Encrypt API 接口,以请求指定的域名的证书。我们利用证书签发者从 Let's Encrypt 获取了证书。然后,我们重新配置了我们的 Ingress,以从集群中的签发者请求证书。

然后,我们深入探讨了身份验证和授权,并向您展示了如何利用 Azure AD 作为留言板应用程序的身份验证提供者。您将学习如何在企业规模上保护您的应用程序。通过与 Azure AD 集成,您可以使任何应用程序连接到组织的 AD。

在下一章中,您将学习如何监视您的部署并设置警报。您还将学习如何在发生错误时快速识别根本原因,并学习如何调试在 AKS 上运行的应用程序。与此同时,您还将学习如何在确定了根本原因后执行正确的修复操作。

第七章:监视 AKS 集群和应用程序

现在您已经知道如何在 AKS 集群上部署应用程序,让我们专注于如何确保您的集群和应用程序保持可用。在本章中,您将学习如何监视您的集群以及运行在其中的应用程序。您将探索 Kubernetes 如何通过就绪和存活探针确保您的应用程序可靠运行。

您还将学习如何使用Azure Monitor,以及它如何在 Azure 门户中集成,以及如何为 AKS 集群上的关键事件设置警报。您将了解如何使用 Azure Monitor 监视集群本身的状态,集群上的 Pod 以及以规模访问 Pod 的日志。

简而言之,本章将涵盖以下主题:

  • 使用kubectl监视和调试应用程序

  • 审查 Kubernetes 报告的指标

  • 审查来自 Azure Monitor 的指标

让我们从回顾一些kubectl中的命令开始,您可以使用这些命令来监视您的应用程序。

用于监视应用程序的命令

监控部署在 Kubernetes 上的应用程序的健康状况以及 Kubernetes 基础架构本身对于向客户提供可靠的服务至关重要。监控有两个主要用例:

  • 持续监控以获取警报,如果某些情况表现不如预期

  • 故障排除和调试应用程序错误

在监视运行在 Kubernetes 集群之上的应用程序时,您需要同时检查多个内容,包括容器、Pod、服务以及集群中的节点。对于持续监控,您将需要像 Azure Monitor 或 Prometheus 这样的监控系统。对于故障排除,您将需要与实时集群进行交互。用于故障排除的最常用命令如下:

kubectl get <resource type> <resource name>
kubectl describe <resource type> <resource name>
kubectl logs <pod name>

我们将在本章中详细描述每个命令。

在开始之前,我们将使用我们的 guestbook 示例进行清洁启动。再次使用以下命令重新创建 guestbook 示例:

kubectl create -f guestbook-all-in-one.yaml

create命令运行时,我们将在以下章节中观察其进展。

kubectl get 命令

为了查看部署应用程序的整体情况,kubectl提供了get命令。get命令列出您指定的资源。资源可以是 Pods、ReplicaSets、Ingresses、节点、部署、Secrets 等等。我们已经在前几章中运行了这个命令,以验证我们的应用程序已经准备好使用。执行以下步骤:

  1. 运行以下get命令,这将获取我们资源及其状态:
kubectl get all

这将显示您的命名空间中的所有部署、ReplicaSets、Pods 和服务:

kubectl get all 命令的输出显示资源及其状态,以及命名空间中的部署、ReplicaSets、Pods 和服务。

图 7.1:默认命名空间中运行的所有资源
  1. 让我们把注意力集中在我们部署中的 Pods 上。我们可以使用以下命令获取 Pods 的状态:
kubectl get pods

您将看到,现在只显示了 Pods,就像图 7.2中所示。让我们详细调查一下:

使用 kubectl get pods 命令检索我们部署中的 Pod 的状态。

图 7.2:您的命名空间中的所有 Pods

第一列表示 Pod 名称,例如frontend-57d8c9fb45-c6qtm。第二列表示 Pod 中准备好的容器数量与 Pod 中的总容器数量。通过 Kubernetes 中的就绪探针来定义就绪状态。我们在本章的后面有一个专门的部分叫做就绪和存活探针

第三列表示状态,例如PendingContainerCreatingRunning等等。第四列表示重启次数,而第五列表示 Pod 被要求创建的时间。

如果您需要更多关于您的 Pod 的信息,您可以通过在命令中添加-o wide来添加额外的列到get命令的输出中,就像这样:

kubectl get pods -o wide

这将显示额外的信息,就像图 7.3中所示:

使用 kubectl get pods -o wide 命令获取有关 Pod 的其他信息。

图 7.3:添加-o wide 显示了 Pod 的更多细节

额外的列包括 Pod 的 IP 地址、它所在的节点、被提名的节点和就绪门。只有当高优先级的 Pod 抢占低优先级的 Pod 时,才会设置被提名的节点。被提名的节点是高优先级 Pod 在低优先级 Pod 优雅终止后将启动的节点。就绪门是引入外部系统组件作为 Pod 就绪的一种方式。

执行get pods命令只显示当前 Pod 的状态。要查看系统中所有资源的事件,请运行以下命令:

kubectl get events

注意

Kubernetes 默认只保留 1 小时的事件。所有命令只在事件在过去一小时内触发时有效。

如果一切顺利,您应该会得到类似于图 7.4的输出:

显示过去一小时事件的 kubectl get events 命令的输出的缩略列表。

图 7.4:获取事件显示过去一小时的所有事件

如您在输出中所见,Pod 的一般状态为Scheduled | Pulling | Pulled | Created | Started。接下来我们将看到,任何状态都可能失败,我们需要使用kubectl describe命令进行深入挖掘。

kubectl describe 命令

使用kubectl get events命令列出整个命名空间的所有事件。如果您只对 Pod 感兴趣,可以使用以下命令:

kubectl describe pods

上述命令列出了所有与所有 Pod 相关的信息。这通常是典型 shell 无法包含的太多信息。

如果您想要特定 Pod 的信息,可以输入以下内容:

kubectl describe pod/<pod-name>

注意

您可以在podpodname之间使用斜杠空格。以下两个命令将产生相同的输出:

kubectl describe pod/<pod-name>

kubectl describe pod <pod-name>

您将得到类似于图 7.5的输出,稍后将对其进行详细解释。

显示指定 pod 的所有细节的 kubectl describe pod/命令的输出。

图 7.5:描述对象显示该对象的详细输出

通过描述,您可以获得 Pod 所在的节点、它运行了多长时间、它的内部 IP 地址、Docker 镜像名称、暴露的端口、env变量和事件(在过去一小时内)。

在上面的示例中,Pod 的名称是frontend-57d8c9fb45-c6qtm。如第一章《Docker 和 Kubernetes 简介》中所述,它采用了“<ReplicaSet 名称>-<随机 5 个字符>”的格式。replicaset名称本身是从部署名称frontend随机生成的:<deployment 名称>-<随机 5 个字符>

图 7.6显示了部署、副本集和 Pod 之间的关系:

图形化表示部署、副本集和 Pod 之间的关系。

图 7.6:部署、副本集和 Pod 之间的关系

Pod 运行的命名空间是default。到目前为止,我们一直在使用名为defaultdefault命名空间。在接下来的章节中,我们将看到命名空间如何帮助我们隔离 Pod。

前面输出中重要的另一部分是节点部分:

Node:aks-agentpool-39838025-vmss000005/10.240.0.7

节点部分让我们知道 Pod 正在哪个物理节点/虚拟机上运行。如果 Pod 反复重启或出现运行问题,而其他一切似乎正常,可能是节点出现了问题。拥有这些信息对于进行高级调试至关重要。

以下是 Pod 最初被调度的时间:

Start Time: Wed, 04 Mar 2020 02:53:55 +0000

这并不意味着 Pod 从那时起一直在运行,因此时间在这方面可能会误导。如果发生健康事件(例如,容器崩溃),Pod 将被重新启动。

资源之间的连接是使用“标签”进行的,如下所示:

Labels:app=guestbook
pod-template-hash=57d8c9fb45
tier=frontend

这就是如何建立Service | Deployment | ReplicaSet | Pod等连接的。如果发现流量没有从 Service 路由到 Pod,这是您应该检查的第一件事。如果标签不匹配,资源将无法连接。

以下显示了 Pod 的内部 IP 和其状态:

Status:Running
IP:10.244.5.52

如前几章所述,构建应用程序时,Pod 可以移动到不同的节点并获得不同的 IP。然而,在调试应用程序问题时,直接获得 Pod 的 IP 可以帮助进行故障排除。您可以直接从一个 Pod 连接到另一个 Pod 以测试连接性,而不是通过 Service 对象连接到应用程序。

在 Pod 中运行的容器和暴露的端口列在以下区块中:

Containers: php-redis:
...
Image:gcr.io/google-samples/gb-frontend:v4
...
Port:80/TCP
Host Port:0/TCP
Environment:
GET_HOSTS_FROM:dns

在这种情况下,我们从gcr.io容器注册表中获取带有v4标签的gb-frontend容器,仓库名称为google-samples

端口80对外部流量开放。由于每个 Pod 都有自己的 IP,即使在同一主机上运行时,相同的端口也可以为同一 Pod 的多个实例开放。例如,如果您在同一节点上运行两个运行 Web 服务器的 Pod,这两个 Pod 都可以使用端口80,因为每个 Pod 都有自己的 IP 地址。这是一个巨大的管理优势,因为您不必担心端口冲突。需要配置的端口也是固定的,因此可以简单地编写脚本,而无需考虑为 Pod 分配了哪个端口的逻辑。

在此处显示了上一个小时发生的任何事件:

Events:

使用kubectl describe非常有用,可以获取有关正在运行的资源的更多上下文。在下一节中,我们将专注于调试应用程序。

调试应用程序

现在我们对如何监视部署有了基本的了解,我们可以开始看看如何调试部署中的问题。

在本节中,我们将介绍常见错误并确定如何调试和修复它们。

如果您尚未实现 guestbook 应用程序,请运行以下命令:

kubectl create -f guestbook-all-in-one.yaml

一段时间后,服务应该已经启动并运行。

图像拉取错误

在本节中,我们将通过将图像标记值设置为不存在的值来介绍图像拉取错误。当 Kubernetes 无法下载容器所需的图像时,就会发生图像拉取错误。

在 Azure Cloud Shell 上运行以下命令:

kubectl edit deployment/frontend

接下来,通过执行以下步骤将图像标记从v4更改为v_non_existent

  1. 键入/gb-frontend并按Enter按钮,将光标移到图像定义处。

  2. 按下I键进入插入模式。删除v4并输入v_non_existent

  3. 现在,首先按下Esc键关闭编辑器,然后输入:wq!并按Enter键。

运行以下命令列出当前命名空间中的所有 Pod:

kubectl get pods

前面的命令应该指示错误,如图 7.7所示:

kubectl get pods 命令的输出显示有三个 Pod 正在运行,而一个 Pod 出现了 Image pull back off 错误。

图 7.7:一个 Pod 的状态为 ImagePullBackOff

运行以下命令以获取完整的错误详细信息:

kubectl describe pods/<failed pod name>

图 7.8显示了一个示例错误输出。关键错误行用红色突出显示:

使用 kubectl describe pods/命令显示无法拉取图像的错误详细信息。图像有两个亮点,“无法拉取图像”和“v_non_existent 未找到”。ils on the error

图 7.8:使用 describe 显示错误的更多详细信息

因此,事件清楚地显示图像不存在。将无效凭据传递给私有 Docker 存储库也会在此处显示。

让我们通过将图像标签设置回v4来修复错误:

  1. 首先,在 Cloud Shell 中键入以下命令以编辑部署:
kubectl edit deployment/frontend
  1. 键入/gb-frontend并按<enter>将光标移到图像定义处。

  2. 按下I键进入插入模式。删除v_non_existent,然后输入v4

  3. 现在,首先按下Esc键关闭编辑器,然后输入:wq!并按Enter键。

部署应该会自动修复。您可以通过再次获取 Pod 的事件来验证它。

注意

由于 Kubernetes 进行了滚动更新,前端一直可用,没有任何停机时间。Kubernetes 识别出新规范存在问题,并停止自动滚动出额外的更改。

图像拉取错误可能发生在图像不可用时。在下一节中,我们将探索应用程序本身的错误。

应用程序错误

我们现在将看到如何调试应用程序错误。本节中的错误将是自我引起的,类似于上一节。调试问题的方法与我们用于调试运行应用程序的方法相同。

为了测试我们的失败,我们将使frontend服务可以公开访问:

  1. 首先,我们将编辑frontend服务:
kubectl edit service frontend
  1. 键入/ClusterIP并按Enter将光标移到类型字段(第 27 行)。

  2. 按下I键进入插入模式。删除ClusterIP,然后输入LoadBalancer

  3. 现在,首先按下Esc键关闭编辑器,然后输入:wq!并按Enter。这将为我们的前端服务创建一个公共 IP。

  4. 我们可以使用以下命令获取此 IP:

kubectl get service
  1. 让我们通过在浏览器中粘贴其公共 IP 来连接到服务。创建一些条目:添加了一些条目的留言板应用程序。
图 7.9:在留言板应用程序中进行一些条目

注意

大多数错误来自错误配置,可以通过编辑规范来修复。应用程序代码本身的错误需要构建和使用新的映像。

您现在有一个运行中的 guestbook 应用程序实例。为了改善示例的体验,我们将缩减前端,以便只有一个副本在运行。

缩减前端

第三章在 AKS 上部署应用程序中,您学习了前端部署的配置为replicas=3。这意味着应用程序接收的请求可以由任何一个 Pod 处理。要引入应用程序错误并注意错误,我们需要对所有三个 Pod 进行更改。

为了使这个示例更容易,将replicas缩减到1,这样您只需要对一个 Pod 进行更改:

kubectl scale --replicas=1 deployment/frontend

只运行一个副本将使引入错误变得更容易。现在让我们引入这个错误。

引入应用程序错误

在这种情况下,我们将使提交按钮无法工作。我们需要修改应用程序代码。

注意

不建议使用kubectl exec对 Pod 执行命令来对应用程序进行生产更改。如果需要对应用程序进行更改,最好的方法是创建新的容器映像并更新部署。

我们将使用kubectl exec命令。此命令允许您在该 Pod 的命令行上运行命令。使用-it选项,它会将交互式终端附加到 Pod,并为我们提供一个可以运行命令的 shell。以下命令在 Pod 上启动 Bash 终端:

kubectl exec -it <frontend-pod-name> bash

一旦您进入容器 shell,运行以下命令:

apt update
apt install -y vim

上述代码安装了 vim 编辑器,以便我们可以编辑文件引入错误。现在,使用vim打开guestbook.php文件:

vim guestbook.php

在第 18 行后添加以下行。记住,在 vim 中插入一行,您按I键。编辑完成后,您可以通过按Esc退出,然后输入:wq!,然后按Enter

$host = 'localhost';
if(!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'w'));
fwrite(STDOUT, "hostname at the beginning of 'set' command "); fwrite(STDOUT, $host);
fwrite(STDOUT, "\n");

文件将看起来像图 7.10

在 vim 编辑器中具有更新代码的 guestbook.php 文件。

图 7.10:引入错误和额外日志的更新代码

我们引入了一个错误,即读取消息可以工作,但写入消息却不行。我们通过要求前端连接到不存在的本地主机的 Redis 主服务器来实现这一点。写入应该失败。与此同时,为了使这个演示更加直观,我们在代码的这一部分添加了一些额外的日志记录。

通过浏览其公共 IP 打开您的留言板应用程序,您应该可以看到之前的条目:

重新打开的留言板应用程序显示之前的条目仍然存在。

图 7.11:之前的条目仍然存在

现在,通过输入一条消息并点击提交按钮来创建一条新消息:

向留言板应用程序添加一个“新消息”条目。

图 7.12:创建了一条新消息

提交新消息会使其出现在我们的应用程序中。如果我们不知道更好的方法,我们可能会认为该条目已经安全地被写入。然而,如果您刷新浏览器,您会发现消息不再存在。

如果您在浏览器中打开了网络调试工具,您可以捕获服务器的错误响应。

为了验证消息没有被写入数据库,点击浏览器中的刷新按钮;您将只看到最初的条目,新条目已经消失:

点击刷新按钮后,留言板应用程序只显示最初的条目。新消息丢失了。

图 7.13:新消息已经消失了

作为应用程序开发人员或操作员,您可能会收到这样的工单:在新部署后,新条目没有被持久化。修复它。

日志

第一步是获取日志。现在让我们暂时退出前端 Pod 并获取该 Pod 的日志:

exit
kubectl logs <frontend-pod-name>

您将看到诸如图 7.14中所见的条目:

kubectl logs 命令的输出显示了该 Pod 的日志。图中包含三个突出显示的区域:“在'设置'命令 localhost 之前的主机名”,“cmd=set”,和“new%20message”。

图 7.14:新消息显示在应用程序日志中

因此,您知道错误出现在代码的set部分写入数据库时。

您将看到这个条目:

hostname at the beginning of 'set' command localhost

所以我们知道错误发生在这一行和客户端开始之间,所以$host = 'localhost'的设置必须是错误的原因。这种错误并不像你想象的那样罕见,正如我们刚才看到的,除非有特定的指示刷新浏览器,否则它很容易通过 QA。对于开发人员来说,它可能完全正常工作,因为他们可能在本地机器上运行着一个 Redis 服务器。

我们现在有两种选项来修复这个错误:我们可以进入 Pod 并进行代码更改,或者我们可以要求 Kubernetes 为我们提供一个健康的新 Pod。不建议对 Pod 进行手动更改,所以我们将使用第二种方法。让我们通过删除有问题的 Pod 来修复这个错误:

kubectl delete pod <podname>

由于我们有一个控制我们的 Pod 的 ReplicaSet,我们应该立即获得一个已经从正确的镜像启动的新 Pod。尝试再次连接到 guestbook 并验证消息是否再次在浏览器刷新时保留。

以下几点总结了一些常见的错误和修复这些错误的方法:

  • 错误可以呈现多种形式。

  • 部署团队遇到的大多数错误都是配置问题。

  • 日志是你的朋友。

  • 在容器上使用kubectl exec是一个有用的调试工具。

  • 请注意,广泛允许kubectl exec是一个严重的安全风险,因为它几乎让 Kubernetes 操作员在他们可以访问的 Pod 中做任何他们想做的事情。

  • 任何打印到stdoutstderr的内容都会显示在日志中(与应用程序/语言/日志框架无关)。

我们在 guestbook 应用程序中引入了一个应用程序错误,并且能够利用 Kubernetes 日志来准确定位代码中的问题。在下一节中,我们将探讨 Kubernetes 中一个强大的机制,称为就绪和活跃探针

就绪和活跃探针

我们在上一节中简要提到了就绪探针。在本节中,我们将更深入地探讨它们。

Kubernetes 使用活跃探针和就绪探针来监视应用程序的可用性。每个探针都有不同的目的:

  • 活跃探针在应用程序运行时监视可用性。如果活跃探针失败,Kubernetes 将重新启动您的 Pod。这对于捕获死锁、无限循环或者只是“卡住”的应用程序可能非常有用。

  • 一个就绪探针监视应用程序何时变得可用。如果就绪探针失败,Kubernetes 将不会向未准备好的 Pod 发送任何流量。如果您的应用程序在变得可用之前必须经过一些配置,或者如果您的应用程序可能会过载但可以从额外的负载中恢复,这将非常有用。

活跃和就绪探针不需要从应用程序的同一端点提供服务。如果您有一个智能应用程序,该应用程序可以在仍然健康的情况下将自身从轮换中移出(意味着不再向应用程序发送流量)。为了实现这一点,就绪探针将失败,但活跃探针仍然保持活动状态。

让我们通过一个例子来构建这个部署。我们将创建两个 nginx 部署,每个部署都有一个索引页面和一个健康页面。索引页面将作为活跃探针。

构建两个 web 容器

在这个例子中,我们将使用一对网页来连接到我们的就绪和活跃探针。让我们首先创建index1.html

<!DOCTYPE html>
<html>
<head>
<title>Server 1</title>
</head>
<body>
Server 1
</body>
</html>

之后,创建index2.html

<!DOCTYPE html>
<html>
<head>
<title>Server 2</title>
</head>
<body>
Server 2
</body>
</html>

我们还将创建一个健康页面,healthy.html

<!DOCTYPE html>
<html>
<head>
<title>All is fine here</title>
</head>
<body>
OK
</body>
</html>

在下一步中,我们将把这些文件挂载到我们的 Kubernetes 部署中。我们将把每个文件都转换成一个configmap,然后连接到我们的 Pods。使用以下命令创建 configmap:

kubectl create configmap server1 --from-file=index1.html
kubectl create configmap server2 --from-file=index2.html
kubectl create configmap healthy --from-file=healthy.html

搞定了这些,我们可以继续创建我们的两个 web 部署。两者将非常相似,只是configmap不同。第一个部署文件(webdeploy1.yaml)如下所示:

1   apiVersion: apps/v1
2   kind: Deployment
...
17     spec:
18       containers:
19         - name: nginx-1
20           image: nginx
21           ports:
22             - containerPort: 80
23           livenessProbe:
24             httpGet:
25               path: /healthy.html
26               port: 80
27             initialDelaySeconds: 3
28             periodSeconds: 3
29           readinessProbe:
30             httpGet:
31               path: /index.html
32               port: 80
33             initialDelaySeconds: 3
34             periodSeconds: 3
35           volumeMounts:
36             - name: html
37               mountPath: /usr/share/nginx/html
38             - name: index
39               mountPath: /tmp/index1.html
40               subPath: index1.html
41             - name: healthy
42               mountPath: /tmp/healthy.html
43               subPath: healthy.html
44           command: ["/bin/sh", "-c"]
45           args: ["cp /tmp/index1.html /usr/share/nginx/html/index.html; cp /tmp/healthy.html /usr/share/nginx/html/healthy.html; nginx; sleep inf"]
46       volumes:
47         - name: index
48           configMap:
49             name: server1
50         - name: healthy
51           configMap:
52             name: healthy
53         - name: html
54           emptyDir: {}

在这个部署中有一些要强调的事项:

  • 第 23-28 行:这是活跃探针。活跃探针指向健康页面。请记住,如果健康页面失败,容器将被重新启动。

  • 第 29-32 行:这是就绪探针。在我们的情况下,就绪探针指向索引页面。如果此页面失败,Pod 将暂时不会收到任何流量,但将继续运行。

  • 第 44-45 行:这两行包含一对在容器启动时执行的命令。我们不仅仅运行 nginx 服务器,而是将索引和就绪文件复制到正确的位置,然后启动 nginx,然后使用一个睡眠命令(这样我们的容器就会继续运行)。

您可以使用以下命令创建此部署。您还可以部署类似于服务器 1 的第二个版本,用于服务器 2:

kubectl create -f webdeploy1.yaml
kubectl create -f webdeploy2.yaml

最后,我们还将创建一个服务,将流量路由到两个部署(webservice.yaml):

1   apiVersion: v1
2   kind: Service
3   metadata:
4     name: web
5   spec:
6     selector:
7       app: web-server
8     ports:
9     - protocol: TCP
10     port: 80
11     targetPort: 80
12   type: LoadBalancer

我们可以使用以下命令创建该服务:

kubectl create -f webservice.yaml

我们现在的应用程序已经启动运行。在接下来的部分,我们将引入一些故障来验证活跃性和就绪探针的行为。

尝试活跃性和就绪探针

在前一部分中,我们解释了活跃性和就绪探针的功能,并创建了一个示例应用程序。在本节中,我们将在应用程序中引入错误,并验证活跃性和就绪探针的行为。我们将看到就绪探针的失败将导致 Pod 保持运行,但不再接受流量。之后,我们将看到活跃性探针的失败将导致 Pod 被重新启动。

让我们首先尝试使就绪探针失败。

使就绪探针失败会导致流量暂时停止

现在我们有一个简单的应用程序正在运行,我们可以尝试活跃性和就绪探针的行为。首先,让我们获取服务的外部 IP 以便使用浏览器连接到我们的 Web 服务器:

kubectl get service

如果您在浏览器中输入外部 IP,您应该看到一个单行,上面要么写着服务器 1,要么写着服务器 2

浏览器显示外部 IP 和应用程序正在从服务器 2 返回流量。

图 7.15:我们的应用程序正在从服务器 2 返回流量

在我们的测试中,我们将使用一个名为testWeb.sh的小脚本连接到我们的网页 50 次,这样我们就可以监视服务器 1 和 2 之间结果的良好分布。我们首先需要使该脚本可执行,然后我们可以在我们的部署完全健康的情况下运行该脚本:

chmod +x testWeb.sh
./testWeb.sh <external-ip>

在健康运行期间,我们可以看到服务器 1 和服务器 2 几乎被同等命中,服务器 1 有 24 次命中,服务器 2 有 26 次命中:

输出显示服务器 1 和服务器 2 的命中实例分别为 24、48、216 和 26、52、234。

图 7.16:当应用程序健康时,流量在服务器 1 和服务器 2 之间进行负载平衡

现在让我们继续并使服务器 1 的就绪探针失败。为此,我们将exec进入容器并将索引文件移动到另一个位置:

kubectl get pods #note server1 pod name
kubectl exec <server1 pod name> mv /usr/share/nginx/html/index.html /usr/share/nginx/html/index1.html

一旦执行了这个,我们可以用以下命令查看 Pod 状态的变化:

kubectl get pods -w

您应该看到服务器 1 Pod 的就绪状态更改为0/1,如图 7.17所示:

使用 kubectl get pods -w 命令的输出显示服务器 1 的就绪状态变为 0/1。

图 7.17:失败的就绪探针导致服务器 1 没有任何 READY 容器

这应该不再将流量定向到服务器 1 的 Pod。让我们验证一下:

./testWeb.sh <external-ip>

在我们的情况下,流量确实被重定向到了服务器 2:

输出显示所有流量都被定向到服务器 2,服务器 1 的流量为零。

图 7.18:现在所有流量都由服务器 2 提供

现在我们可以通过将文件移回到其正确位置来恢复服务器 1 的状态:

kubectl exec <server1 pod name> mv /usr/share/nginx/html/index1.html /usr/share/nginx/html/index.html

这将使我们的 Pod 恢复到健康状态,并应该再次平均分配流量:

./testWeb.sh <external-ip>

这将显示类似于图 7.19的输出:

输出显示就绪探针导致流量再次在服务器 1 和服务器 2 之间进行负载均衡。

图 7.19:恢复就绪探针会导致流量再次进行负载均衡

失败的就绪探针将导致 Kubernetes 不再向失败的 Pod 发送流量。我们通过使示例应用程序中的就绪探针失败来验证了这一点。在下一节中,我们将探讨失败的活跃探针的影响。

失败的活跃探针导致容器重新启动

我们也可以使用活跃探针重复之前的过程。当活跃探针失败时,我们期望 Kubernetes 会继续重启我们的 Pod。让我们尝试通过删除健康文件来实现这一点:

kubectl exec <server1 pod name> rm /usr/share/nginx/html/healthy.html

让我们看看这对我们的 Pod 有什么影响:

kubectl get pods -w

我们应该看到 Pod 在几秒钟内重新启动:

使用 kubectl get pods -w 跟踪 Pod 状态的图像。图像显示服务器 1 通过将重启计数器从 0 增加到 1 来重新启动。

图 7.20:失败的活跃探针将导致 Pod 重新启动

正如您在图 7.20中所看到的,Pod 已成功重新启动,影响有限。我们可以通过运行describe命令来检查 Pod 中发生了什么:

kubectl describe pod <server1 pod name>

上述命令将给出类似于图 7.21的输出:

使用 kubectl describe pod 命令的输出提供了有关 Pod 的额外细节,并显示了活跃探针的失败情况。

图 7.21:有关 Pod 的更多详细信息,显示了活跃探针的失败情况

describe命令中,我们可以清楚地看到 Pod 未通过活跃探针。在四次失败后,容器被终止并重新启动。

这结束了我们对活跃性和就绪性探针的实验。请记住,这两者对您的应用程序都很有用:就绪性探针可用于暂时停止流量到您的 Pod,以便它承受更少的负载。活跃性探针用于在 Pod 出现实际故障时重新启动 Pod。

让我们也确保清理我们刚刚创建的部署:

kubectl delete deployment server1 server2
kubectl delete service web

活跃性和就绪性探针对确保只有健康的 Pod 会在您的集群中接收流量很有用。在下一节中,我们将探索 Kubernetes 报告的不同指标,您可以使用这些指标来验证应用程序的状态。

Kubernetes 报告的指标

Kubernetes 报告多个指标。在本节中,我们首先将使用一些 kubectl 命令来获取这些指标。之后,我们将研究 Azure 容器监视器,看看 Azure 如何帮助容器监视。

节点状态和消耗

您的 Kubernetes 中的节点是运行应用程序的服务器。Kubernetes 将 Pod 调度到集群中的不同节点。您需要监视节点的状态,以确保节点本身健康,并且节点有足够的资源来运行新应用程序。

运行以下命令以获取有关集群上节点的信息:

kubectl get nodes

上述命令列出它们的名称、状态和年龄:

kubectl get nodes 命令的输出,列出节点的名称、状态和年龄。

图 7.22:此集群中有两个节点

您可以通过传递-o wide 选项来获取更多信息:

kubectl get -o wide nodes

输出列出了底层的OS-IMAGEINTERNAL-IP,以及其他有用的信息,可以在图 7.23中查看。

kubectl get -o wide nodes 命令的输出,显示节点的额外信息,如内部 IP、外部 IP、内核版本和容器运行时。

图 7.23:使用-o wide 可以添加关于我们节点的更多细节

您可以使用以下命令找出哪些节点消耗了最多的资源:

kubectl top nodes

它显示了节点的 CPU 和内存使用情况:

kubectl top nodes 命令的输出,显示节点的 CPU 和内存利用率。

图 7.24:节点的 CPU 和内存利用率

请注意,这是那一时刻的实际消耗,而不是某个节点的请求数。要获取请求数,您可以执行:

kubectl describe node <node name>

这将向您显示每个 Pod 的请求和限制,以及整个节点的累积量:

显示特定节点分配的请求和限制数量的输出。

图 7.25:描述节点显示有关请求和限制的详细信息

现在您知道在哪里可以找到有关节点利用率的信息。在下一节中,我们将探讨如何获取单个 Pod 的相同指标。

Pod 消耗

Pods 从 AKS 集群中消耗 CPU 和内存资源。请求和限制用于配置 Pod 可以消耗多少 CPU 和内存。请求用于保留最小数量的 CPU 和内存,而限制用于设置每个 Pod 的最大 CPU 和内存量。

在本节中,我们将探讨如何使用kubectl获取有关 Pod 的 CPU 和内存利用率的信息。

让我们首先探索如何查看当前正在运行的 Pod 的请求和限制:

  1. 在这个例子中,我们将使用在kube-system命名空间中运行的 Pods。获取此命名空间中的所有 Pods:
kubectl get pods -n kube-system

这应该显示类似于图 7.26的内容:

使用 kubectl get pods -n kube-system 列出 kube-system 命名空间中所有 Pod 的名称、状态、重启次数和年龄。

图 7.26:在 kube-system 命名空间中运行的 Pods
  1. 让我们获取coredns Pods 之一的请求和限制。可以使用describe命令来完成:
kubectl describe pod coredns-<pod id> -n kube-system

describe命令中,应该有一个类似于图 7.27的部分:

显示 CoreDNS Pod 的限制和请求的 describe 命令。

图 7.27:CoreDNS Pod 的限制和请求

这向我们显示,此 Pod 的内存限制为170Mi,没有 CPU 限制,并且请求了 100m CPU(即 0.1 CPU)和70Mi内存。

请求和限制用于在集群中执行容量管理。我们还可以通过运行以下命令获取 Pod 的实际 CPU 和内存消耗:

kubectl top pods -n kube-system

这应该向您显示类似于图 7.28的输出:

显示 Pod 的 CPU 和内存消耗的 kubectl top pods -n kube-system 命令的输出。

图 7.28:查看 Pod 的 CPU 和内存消耗

使用 kubectl top 命令显示了命令运行时的 CPU 和内存消耗。在这种情况下,我们可以看到 coredns Pods 正在使用 2m 和 3m 的 CPU,并且正在使用 21Mi 和 17Mi 的内存。

在本节中,我们一直在使用 kubectl 命令来了解集群中节点和 Pods 的资源利用情况。这是有用的信息,但仅限于特定时间点。在下一节中,我们将使用 Azure Monitor 来获取有关集群和集群上应用程序的更详细信息。

来自 Azure Monitor 的报告指标

Azure 门户显示了许多指标,您希望将其与授权结合在一起,因为只有具有门户访问权限的人员才能查看这些指标。

AKS Insights

AKS 刀片的 Insights 部分提供了您需要了解有关集群的大多数指标。它还具有深入到容器级别的能力。您还可以查看容器的日志。

Kubernetes 提供了可用的指标,但不会存储它们。Azure Monitor 可用于存储这些指标,并使它们随时间可用于查询。为了将相关指标和日志收集到 Insights 中,Azure 连接到 Kubernetes API 来收集指标,然后将其存储在 Azure Monitor 中。

注意

容器的日志可能包含敏感信息。因此,应控制和审计查看日志的权限。

让我们探索 AKS 刀片的 Insights 选项卡。我们将从集群指标开始。

集群指标

Insights 显示集群指标。图 7.29 显示了集群中所有节点的 CPU 利用率和内存利用率:

Insights 选项卡中的 Cluster 选项卡显示了集群指标。它显示了两个图表:集群中所有节点的 CPU 利用率和内存利用率。

图 7.29:Cluster 选项卡显示了集群的 CPU 和内存利用率

集群指标还显示了节点计数和活动 Pods 的数量。节点计数非常重要,因为您可以跟踪是否有节点处于 Not Ready 状态:

Cluster 选项卡显示两个额外的图表:节点计数和活动 Pods 的数量。

图 7.30:Cluster 选项卡显示了节点计数和活动 Pods 的数量

Cluster 选项卡可用于监视集群中节点的状态。接下来,我们将探索 Health 选项卡。

使用健康选项卡

在撰写本书时,“健康”选项卡处于预览状态。该选项卡显示了集群健康状况的视图。为了向您显示这一状态,Azure 监视并检查所需的基础设施组件以及节点健康状况:

健康选项卡的视图显示了集群的整体健康状况。该集群的所有健康指标都标记为绿色。

图 7.31:健康选项卡显示了集群的整体健康状况

“健康”选项卡对于跟踪集群的整体健康状况非常有用。我们将要探索的下一个选项卡是“节点”选项卡。

节点

“节点”视图向您显示了节点的详细指标。它还向您显示了每个节点上运行的 Pods,正如我们在图 7.32中所看到的:

节点视图窗格显示了每个节点上运行的 Pod 的详细指标。

图 7.32:节点视图窗格中节点的详细指标

如果您想要更多细节,您还可以单击并从节点获取 Kubernetes 事件日志:

右侧窗格中的查看 Kubernetes 事件日志选项允许从集群获取日志。

图 7.33:单击“查看 Kubernetes 事件日志”以从集群获取日志

这将打开 Azure Log Analytics,并为您预先创建一个查询,显示节点的日志。在我们的情况下,我们可以看到我们的节点已经重新启动了几次:

Log Analytics 显示了节点的日志。

图 7.34:Log Analytics 显示了节点的日志

控制器

“控制器”视图向您显示了集群中所有控制器(即 ReplicaSet、DaemonSet 等)的详细信息以及其中运行的 Pods。这向您展示了一个以控制器为中心的运行容器的视图。例如,您可以找到前端 ReplicaSet 并查看其中运行的所有 Pods 和容器,如图 7.35所示:

在控制器选项卡中显示的具有名称和状态的 ReplicaSet 中运行的所有容器。

图 7.35:控制器选项卡向我们显示了 ReplicaSet 中运行的所有容器

接下来的选项卡是“容器”选项卡,它将向我们显示容器的指标、日志和环境变量。

容器指标、日志和环境变量

单击“容器”选项卡会列出容器指标、环境变量以及访问其日志,如图 7.36所示:

容器选项卡显示了所有单独的容器。

图 7.36:容器选项卡显示了所有单独的容器

注意

您可能会注意到一些容器处于“未知”状态。在我们的情况下,这是可以预料的。在 Azure Monitor 中,我们的时间范围设置为过去 6 小时,在过去 6 小时内,我们创建并删除了许多 Pod。它们不再存在,但 Azure Monitor 知道它们的存在,甚至为它们保留了日志。

我们可以从这个视图中访问我们容器的日志:

在容器选项卡中访问容器的日志的选项。

图 7.37:访问容器的日志

这向我们展示了 Kubernetes 从我们的应用程序中记录的所有日志。我们在本章前面手动访问了这些日志。使用这种方法可能会更加高效,因为我们可以编辑日志查询并在单个视图中关联来自不同 Pod 和应用程序的日志:

通过日志窗口编辑日志查询并在单个视图中关联来自不同 Pod 和应用程序的日志。

图 7.38:日志已被收集并可以查询

除了日志之外,这个视图还显示了为容器设置的环境变量。要查看环境变量,请在此视图的右侧单元格中向下滚动:

通过在日志视图的右侧单元格中向下滚动来显示为容器设置的环境变量。

图 7.39:为容器设置的环境变量

以上就是本节的内容。让我们确保清理我们的部署,这样我们就可以在下一章中继续使用一个干净的留言板:

kubectl delete -f guestbook-all-in-one.yaml

在本节中,我们探讨了在 Kubernetes 上运行的应用程序的监控。我们在 Azure 门户中使用 AKS Insights选项卡来详细查看我们的集群和运行在集群上的容器。

总结

我们在本章开始时展示了如何使用不同的kubectl命令来监视应用程序。然后,我们展示了 Kubernetes 创建的日志如何用于调试该应用程序。日志包含写入stdoutstderr的所有信息。最后,我们解释了使用 Azure Monitor 来显示 AKS 指标和环境变量,以及使用日志过滤显示日志。我们还展示了如何使用kubectl和 Azure Monitor 监控来调试应用程序和集群问题。

在下一章中,我们将学习如何将 AKS 集群连接到 Azure PaaS 服务。我们将重点关注如何将 AKS 集群连接到 Azure 管理的 MySQL 数据库。

第三部分:利用高级 Azure PaaS 服务

在本书的这一部分中,我们已经在 AKS 之上运行了多个应用程序。这些应用程序总是自包含的,这意味着整个应用程序能够完整地在 AKS 之上运行。在 AKS 之上运行完整的应用程序有一定的优势。您可以获得应用程序的可移植性,因为您可以将该应用程序移动到任何其他 Kubernetes 集群而几乎没有摩擦。您还可以完全控制端到端的应用程序。

伴随着更大的控制权而来的是更大的责任。将应用程序的部分内容卸载到 Azure 提供的 PaaS 服务中有一定的优势。例如,通过将数据库卸载到托管的 PaaS 服务,您不再需要关心更新数据库服务,备份会自动为您执行,并且大量的日志记录和监控都是开箱即用的。

在接下来的章节中,我们将介绍多种高级集成和随之而来的优势。阅读完本节后,您应该能够安全地访问其他 Azure 服务,如 Azure SQL 数据库、事件中心和 Azure 函数。

本节包括以下章节:

  • 第八章,将应用程序连接到 Azure 数据库

  • 第九章,连接到 Azure 事件中心

  • 第十章,保护您的 AKS 集群

  • 第十一章,无服务器函数

我们将从第八章开始,将应用程序连接到 Azure 数据库,在这一章中,我们将把一个应用程序连接到一个托管的 Azure 数据库。

第八章:连接应用程序到 Azure 数据库

在之前的章节中,我们将应用程序的状态存储在我们的集群中,要么在 Redis 集群上,要么在 MariaDB 上。您可能还记得在高可用性方面两者都存在一些问题。本章将带您了解连接到 Azure 托管的 MySQL 数据库的过程。

我们将讨论使用托管数据库与在 Kubernetes 本身上运行 StatefulSets 的好处。为了创建这个托管和管理的数据库,我们将利用 Azure 的 Open Service Broker for Azure(OSBA)。OSBA 是一种从 Kubernetes 集群内部创建 Azure 资源(如托管的 MySQL 数据库)的方式。在本章中,我们将更详细地解释 OSBA 项目,并在我们的集群上设置和配置 OSBA。

然后,我们将利用 OSBA 在 Azure 中创建一个 MySQL 数据库。我们将把这个托管数据库作为 WordPress 应用程序的一部分。这将向您展示如何将应用程序连接到托管数据库。

此外,我们还将向您展示安全性、备份、灾难恢复(DR)、授权和审计日志的方面。还将探讨数据库和集群的独立扩展。我们将把本章的讨论分解为以下主题:

  • OSBA 的设置

  • 扩展我们的应用程序以连接到 Azure 数据库

  • 探索高级数据库操作

  • 审查审计日志

让我们从在我们的集群上设置 OSBA 开始。

设置 OSBA

在本节中,我们将在我们的集群上设置 OSBA。OSBA 将允许我们在不离开 Kubernetes 集群的情况下创建一个 MySQL 数据库。我们将从解释使用托管数据库与在 Kubernetes 本身上运行 StatefulSets 的好处开始本节。

使用托管数据库服务的好处

到目前为止,我们所讨论的所有示例都是自包含的,也就是说,一切都在 Kubernetes 集群内运行。几乎任何生产应用程序都有状态,通常存储在数据库中。虽然在大部分情况下基本上是云无关的有很大的优势,但在管理数据库等有状态工作负载时却有很大的劣势。

当您在 Kubernetes 集群上运行自己的数据库时,您需要关注可伸缩性、安全性、高可用性、灾难恢复和备份。云提供商提供的托管数据库服务可以减轻您或您的团队执行这些任务的负担。例如,Azure Database for MySQL 具有企业级安全性和合规性、内置高可用性和自动备份。该服务可以在几秒钟内扩展,并且可以非常容易地配置为灾难恢复。

从 Azure 消费生产级数据库要比在 Kubernetes 上设置和管理自己的数据库简单得多。在下一节中,我们将探讨 Kubernetes 如何用于在 Azure 上创建这些数据库的方法。

什么是 OSBA?

在本节中,我们将探讨 OSBA 是什么。

与如今大多数应用程序一样,大部分工作已经由开源社区(包括微软员工)为我们完成。微软已经意识到许多用户希望从 Kubernetes 使用其托管服务,并且他们需要一种更容易使用与 Kubernetes 部署相同方法的方式。为了支持这一努力,他们发布了使用这些托管服务作为后端的 Helm 图表 (github.com/Azure/helm-charts)。

允许您从 Kubernetes 内部创建 Azure 资源的架构的关键部分是 OSBA (osba.sh/)。OSBA 是 Azure 的开放服务经纪人OSB)实现。OSB API 是一个规范,定义了云原生应用程序可以使用的平台提供商的通用语言,以管理云服务而无需锁定。

OSB API 本身并不是针对 Azure 或 Kubernetes 的特定。这是一个简化资源供应的行业努力,通过标准化 API 来连接第三方服务。

在使用 Kubernetes 的 OSB API 时,集群上会运行一个名为服务目录的扩展。服务目录将监听 Kubernetes API 的请求,并将其转换为 OSB API,以与平台提供商进行接口交互。这意味着当您请求数据库时,Kubernetes API 将该请求发送到服务目录,然后服务目录将使用 OSB API 与平台进行接口交互。图 8.1说明了这种逻辑流程:

用户向 Kubernetes API 请求数据库。然后 Kubernetes API 将此请求转发给服务目录。服务目录然后使用平台上的 OSB API 创建数据库。

图 8.1:在 Kubernetes 集群上使用 OSB 请求数据库的逻辑流程

OSBA 是用于多个 Azure 服务的 OSB 实现。它允许用户使用 OSB API 创建 14 种支持的 Azure 服务中的任何一种。其中一个服务是 Azure Database for MySQL。这意味着您可以通过 OSBA 在 Azure 上定义一个 MySQL 数据库,而无需使用 Azure 门户。

在下一节中,我们将专注于如何在我们的集群上安装 OSBA。

在集群上安装 OSBA

我们将在我们的集群上安装 OSBA。这个安装有两个元素。首先,我们将在我们的集群上安装服务目录扩展。之后,我们可以在集群上安装 OSBA。

由于我们将在我们的集群上安装多个组件,我们的双节点集群不足以满足这个示例。让我们主动将我们的 AKS 集群扩展到三个节点,这样我们在这个示例中就不会遇到任何问题:

az aks scale -n <clustername> -g <cluster resource group> -c 3

这个扩展将需要几分钟时间。当集群扩展到三个节点时,我们可以开始部署服务目录到集群上。

在集群上部署服务目录

服务目录提供了 OSB 所需的目录服务器。要在集群上部署服务目录,请按照以下步骤进行:

  1. 让我们通过运行以下命令来部署服务目录:
kubectl create namespace catalog
helm repo add svc-cat https://svc-catalog-charts.storage.googleapis.com
helm install catalog svc-cat/catalog --namespace catalog
  1. 等待服务目录部署完成。您可以通过运行以下命令来检查:
kubectl get all -n catalog
  1. 验证部署中的两个 Pod 都是Running并且完全准备就绪:输出屏幕表示使用 kubectl get all -n catalog 命令成功部署服务目录。您将看到创建了四种类型的对象,即一个目录 Pod,服务目录,部署目录和复制集目录。
图 8.2:成功部署服务目录
  1. 要与服务经纪人进行交互,我们需要安装另一个 CLI 工具,即svcat。我们可以使用以下命令来完成:
curl -sLO https://download.svcat.sh/cli/latest/linux/amd64/svcat
chmod +x ./svcat
./svcat version --client

我们现在在我们的集群上配置了一个服务目录。现在,我们可以继续在集群上安装 OSBA。

部署 OSBA

在本节中,我们将在我们的集群上部署实际的 OSBA。对于这个设置,我们需要获取订阅 ID、租户 ID、客户 ID 和 OSBA 启动 Azure 服务的凭据:

  1. 运行以下命令以获取所需的列表:
az account list

输出将如图 8.3所示:

屏幕显示所需列表(订阅 ID 和租户 ID),使用 az account list 命令。

图 8.3:显示所需列表-订阅 ID 和租户 ID 的输出
  1. 复制您的订阅 ID以及租户 ID并将其保存在环境变量中:
export AZURE_SUBSCRIPTION_ID="<SubscriptionId>"
export AZURE_TENANT_ID="<Tenant>"
  1. 创建一个启用了 RBAC 的服务主体,以便它可以启动 Azure 服务。如果您与其他人共享订阅,请确保服务主体的名称在您的目录中是唯一的:
az ad sp create-for-rbac --name osba-quickstart -o table

这将生成一个如图 8.4所示的输出:

输出显示服务主体凭据,如 AppID、DisplayName、Name 和密码,使用 az ad sp create-for-rbac --name osba-quickstart -o table 命令。

图 8.4:显示服务主体凭据的输出

注意

为了使上一步成功完成,您需要在 Azure 订阅上拥有所有者角色。

  1. 将命令输出的值保存在环境变量中:
export AZURE_CLIENT_ID="<AppId>"
export AZURE_CLIENT_SECRET="<Password>"
  1. 现在,我们可以按以下方式部署 OSBA:
kubectl create namespace osba
helm repo add azure https://kubernetescharts.blob.core.windows.net/azure
helm install osba azure/open-service-broker-azure \
--namespace osba \
  --set azure.subscriptionId=$AZURE_SUBSCRIPTION_ID \
  --set azure.tenantId=$AZURE_TENANT_ID \
  --set azure.clientId=$AZURE_CLIENT_ID \
  --set azure.clientSecret=$AZURE_CLIENT_SECRET

为了验证一切都正确部署了,您可以运行以下命令:

kubectl get all -n osba

等待直到两个 Pod 都处于Running状态。如果其中一个 Pod 处于Error状态,您不必担心。OSBA Pods 将自动重新启动并应达到健康状态。在我们的情况下,一个 Pod 重新启动了三次,如图 8.5所示:

输出将显示 OSBA Pods 的状态为 Running,使用 kubectl get all -n osba 命令。在这里,两个 Pod 都处于 Running 状态,其中一个 Pod 重新启动了三次。

图 8.5:显示 OSBA Pods 处于 Running 状态的输出
  1. 为了验证我们的部署完全成功,我们可以使用我们在上一节中下载的svcat实用程序:
./svcat get brokers

这应该显示您的 Azure 经纪人:

您可以使用./svcat get brokers 命令查看集群中运行的 Azure 经纪人。

图 8.6:显示集群中运行的 Azure 经纪人的输出
  1. 您还可以验证通过 OSBA 驱动程序可以部署的所有服务:
./svcat get classes

这将显示可以使用 OSBA 创建的服务列表,如图 8.7所示:

使用./svcat get classes 命令可以获取(裁剪后的)服务列表。在这里,您可以看到名称以及描述。名称和描述之间的命名空间列为空白。

图 8.7:可以使用 OSBA 创建的服务(裁剪后)列表

在本节中,我们在我们的集群上设置了服务目录和 OSBA。这意味着我们现在可以通过 Azure 从我们的集群创建托管服务。在下一节中,当我们使用 Azure 托管的数据库部署 WordPress 时,我们将使用这种能力。

部署 WordPress

以下是部署 WordPress 的步骤:

  1. 运行以下命令安装 WordPress:
kubectl create ns wordpress
helm install wp azure/wordpress --namespace wordpress --set replicaCount=1 --set externalDatabase.azure.location=<your Azure region>
  1. 要验证 WordPress Pod 的状态,请运行以下命令:
kubectl get pods -n wordpress

这应该显示单个 WordPress Pod 的状态,如图 8.8所示。在我们之前的 WordPress 示例中,我们总是有两个运行的 Pod,但是我们能够在这里将数据库功能卸载到 Azure:

这张图片显示了 wordpress 部署只创建了一个单独的 pod。您将看到 pod 的名称以及它是否处于 Ready 状态。它还显示了 ContainerCreating 的状态,它重新启动的次数以及获取此状态所花费的时间。

图 8.8:仅显示一个 WordPress Pod 和我们集群中没有数据库的输出
  1. 在创建 WordPress Pod 时,我们还可以检查数据库的状态。我们可以使用两种工具来获取此状态,要么是svcat,要么是kubectl
./svcat get instances -n wordpress

这将生成如图 8.9所示的输出:

使用./svcat get instances -n wordpress 命令,您可以获取您的 MySQL 实例。您将看到名称,命名空间,类别,计划和状态。

图 8.9:显示使用 svcat 获取我们的 MySQL 实例的输出

我们可以使用kubectl获得类似的结果:

kubectl get serviceinstances -n wordpress

这将生成如图 8.10所示的输出:

输出代表使用 svcat 使用 kubectl get serviceinstances -n wordpress 命令获取我们的 MySQL 实例。您将看到名称,类别,计划,状态和年龄。

图 8.10:显示使用 kubectl 获取我们的 MySQL 实例的输出

正如您所看到的,每种方法的输出都是相似的。

  1. 请给部署几分钟的时间来完成。首先,需要完全配置数据库,然后 WordPress Pod 需要进入“运行”状态。要验证一切是否正常运行,请检查 WordPress Pod 的状态,并确保它处于“运行”状态:
kubectl get pods -n wordpress

这将生成一个如图 8.11所示的输出:

输出显示了使用 kubectl 获取我们的 MySQL 实例的 kubectl get pods -n wordpress 命令的输出。在一段时间后,您将看到状态更改为 Running。

图 8.11:显示 WordPress Pod 的状态的输出

我们现在已经部署了使用 Azure 托管数据库的 WordPress。但是,默认情况下,对我们的数据库的连接是对互联网开放的。我们将在下一节中更改这一点。

保护 MySQL

尽管许多步骤都是自动化的,但这并不意味着我们的 MySQL 数据库已经可以投入生产。例如,MySQL 服务器的网络设置具有允许来自任何地方的流量的默认规则。我们将把这个更改为更安全的服务端点规则,也称为VNet 规则

在 Azure 中,服务端点是您用于部署的网络(也称为 VNet)与其连接的服务之间的安全连接。在 AKS 和 MySQL 的情况下,这将在 AKS 部署的 VNet 和 MySQL 服务之间建立安全连接。

在本节中,我们将配置我们的 MySQL 数据库以使用服务端点:

  1. 要进行此更改,请在 Azure 搜索栏中搜索mysql在 Azure 搜索栏中键入 mysql 以找到它。
图 8.12:在 Azure 搜索栏中搜索 MySQL
  1. 在 MySQL 资源的资源页面中,转到左侧导航中的连接安全在 MySQL 资源的资源页面中,单击左侧的连接安全选项卡。
图 8.13:点击连接安全
  1. 有一个默认规则允许从任何 IP 地址连接到数据库。您可以将 AKS VNet 添加到VNet 规则部分,并删除AllowAll 0.0.0.0规则,如图 8.14所示:在 Azure Database for MySQL 服务器中,单击位于屏幕左侧的连接安全选项卡。这将允许您将 AKS VNet 添加到 VNet 规则部分并删除 AllowAll 规则。
图 8.14:将您的 AKS VNet 添加到 VNet 规则部分并删除 AllowAll 规则

通过进行这个简单的改变,我们大大减少了攻击面。现在我们可以连接到我们的 WordPress 网站。

连接到 WordPress 网站

您可以通过使用EXTERNAL_IP来验证您的博客网站是否可用和运行,该 IP 是通过运行以下命令获得的:

kubectl get service -n wordpress

这将生成一个如图 8.15所示的输出:

使用 kubectl get service -n wordpress 命令显示服务的外部 IP。

图 8.15:显示服务外部 IP 的输出

然后,打开一个网页浏览器,转到http://<EXTERNAL_IP>/。您应该会看到您全新的博客:

屏幕将显示 WordPress 博客的默认外观。

图 8.16:WordPress 博客的最终外观

在本节中,我们启动了一个由 Azure 托管的 WordPress 网站,并对其进行了防火墙修改以确保安全。在接下来的部分,我们将介绍让 Azure 管理数据库的优势。

探索高级数据库操作

在 Azure 顶层作为托管服务运行数据库有许多优势。在本节中,我们将探讨这些好处。我们将探讨从备份中恢复,如何设置灾难恢复以及如何访问审计日志以验证谁对您的数据库进行了更改。

我们将从备份中恢复我们的数据库开始。

从备份恢复

当您在 Kubernetes 集群中运行数据库时,高可用性HA)、备份和灾难恢复是您的责任。让我们花点时间来解释这三个概念之间的区别:

  • HA:HA 指的是服务的本地冗余,以确保在单个组件故障时服务仍然可用。这意味着设置服务的多个副本并协调它们之间的状态。在数据库上下文中,这意味着设置数据库集群。

Azure Database for MySQL 服务内置了 HA。在撰写本文时,它提供了每月 99.99%的可用性 SLA。

  • 备份:备份是指对数据进行历史性的复制。当数据发生意外情况时,如意外数据删除或数据被覆盖时,备份是非常有用的。如果您自己运行数据库,您需要设置cron作业来进行备份并将其单独存储。

Azure Database for MySQL 会自动处理备份,无需额外配置。该服务每 5 分钟进行一次备份,并使您能够恢复到任何时间点。备份默认保留 7 天,可选配置使备份保留时间延长至 25 天。

  • DR:DR 指的是系统从灾难中恢复的能力。这通常指的是从完全区域性停机中恢复的能力。如果您运行自己的数据库,这将涉及在第二区域设置一个辅助数据库,并将数据复制到该数据库。

在 Azure Database for MySQL 的情况下,很容易配置 DR。该服务可以设置一个辅助托管数据库,并将数据从主要区域复制到辅助区域。

注意

您可以参考docs.microsoft.com/azure/mysql/concepts-backup获取有关备份频率、复制和恢复选项的最新信息。

术语 HA、备份和 DR 经常被混淆。使用正确的术语并理解这三个概念之间的区别非常重要。在本节中,我们将重点关注备份,并从我们的 WordPress 数据库执行恢复。为了证明恢复操作将恢复用户数据,我们将首先创建一个博客帖子。

在 WordPress 上创建博客帖子

我们将创建一篇博客文章,以证明恢复操作将捕获我们在数据库上生成的新数据。为了能够发布这篇文章,我们需要我们站点的管理员凭据。我们将首先获取这些凭据,然后发布一篇新文章:

  1. 要获取管理员凭据,请使用以下命令:
echo Password: $(kubectl get secret --namespace wordpress \
  wp-wordpress -o jsonpath="{.data.wordpress-password}" | \
  base64 --decode)

这将显示您连接到管理员网站的密码:

使用 Helm 提供的命令获取 WordPress 管理员用户的密码。

图 8.17:获取管理员凭据
  1. 现在浏览http://<EXTERNAL IP>/admin以打开 WordPress 站点的管理页面。使用用户名user和上一步的密码登录。

  2. 连接后,选择撰写您的第一篇博客文章链接:在仪表板上,您将看到一个欢迎消息,您将在“下一步”标题下找到“撰写您的第一篇博客文章”的链接。单击此链接。

图 8.18:单击链接撰写帖子
  1. 创建一篇博客文章。内容并不重要。一旦您满意您的博客文章,选择发布按钮保存并发布博客文章:在博客上输入内容后,转到窗口右侧的发布选项卡。点击该选项卡下的发布按钮来保存并发布。
图 8.19:创建一个示例博客文章并点击发布按钮保存
  1. 您现在可以连接到http://<EXTERNAL IP>来查看您的博客文章:单击发布按钮后,您将看到一个白屏,上面有一条消息-这篇博客文章将确认备份和恢复。这显示了博客文章的成功状态。
图 8.20:显示博客文章成功状态的提示

现在我们已经保存了一篇博客文章,请至少等待 5 分钟。Azure 每 5 分钟对 MySQL 数据库进行一次备份,我们要确保我们的新数据已经备份。一旦过了这 5 分钟,我们就可以继续下一步,执行实际的恢复。

执行恢复

现在我们的博客和数据库中都有实际内容。假设在更新过程中,数据库损坏了,所以我们想进行时间点恢复:

  1. 要开始恢复操作,请在 Azure 门户中的 MySQL 选项卡上点击恢复在 Azure Database for MySQL servers 门户中,您会看到屏幕顶部有一个“恢复”按钮。点击它来启动恢复过程。
图 8.21:点击恢复按钮来启动恢复过程
  1. 然后,您需要选择要执行恢复的时间点。这个时间点可以是当前时间。给恢复的数据库起一个名字,这个名字必须是唯一的,如图 8.22所示。最后,点击确定。大约 5 到 10 分钟后,MySQL 服务应该会恢复:单击“恢复”按钮后,将会弹出一个窗口,您需要在其中输入要执行恢复的日期和时间。给恢复的数据库起一个唯一的名字,然后点击确定。
图 8.22:选择要恢复的时间点并点击确定按钮

在本节中,我们恢复了我们的 MySQL 数据库。当恢复操作完成后,还有一步需要完成,那就是将 WordPress 连接到恢复的数据库。

将 WordPress 连接到恢复的数据库

恢复操作创建了数据库的新实例。为了使我们的 WordPress 安装连接到恢复的数据库,我们需要修改 Kubernetes 部署文件。理想情况下,您将修改 Helm 值文件并执行 Helm 升级;然而,这超出了本书的范围。以下步骤将帮助您将 WordPress 连接到恢复的数据库:

  1. 从 Azure 门户中,记下服务器名称,如图 8.23所示:屏幕显示了恢复的数据库的详细信息。记下门户右上角给出的服务器名称。
图 8.23:显示恢复的数据库的完整名称
  1. 还要修改连接安全性,就像之前一样,以允许集群与恢复的数据库通信。删除所有规则并向 AKS 集群的网络添加 VNet 规则。结果如图 8.24所示:在 Azure Database for MySQL 服务器中,单击位于屏幕左侧导航窗格上的连接安全选项卡。当您编辑信息时,屏幕将显示“未配置防火墙”的消息。
图 8.24:编辑恢复的数据库的连接安全性
  1. 接下来,我们需要将我们的 WordPress Pod 连接到新的数据库。让我们指出这是如何发生的。要获取这些信息,请运行以下命令:
kubectl describe deploy wp -n wordpress

您可以看到连接到数据库的值是从一个 secret 中获取的,如图 8.25所示:

输出显示一系列环境变量,如主机、端口号、数据库名称、数据库用户和密码。连接数据库到 WordPress 的值是从一个 Secret 中获取的。

图 8.25:显示 WordPress Pod 的环境变量

在设置 WordPress 时,安装程序将在文件/bitname/wordpress/wp-config.php中保存此配置。在接下来的步骤中,我们将首先编辑 secret,然后重新配置wp-config.php

  1. 要设置 secrets,我们需要base64值。通过运行以下命令获取服务器名称的base64值:
echo <restored db server name> | base64

注意base64值。

  1. 现在,我们将继续编辑 Secret 中的主机名。为此,我们将使用edit命令:
kubectl edit secret wp-wordpress-mysql-secret -n wordpress

这将打开一个vi编辑器。导航到包含host的行并按I按钮。删除主机的当前值,并粘贴新的base64编码值。然后按Esc,输入:wq!,然后按Enter。您的密钥应如图 8.26所示:

打开 vi 编辑器后,导航到主机行。删除主机的当前值,并粘贴 base64 编码的值。这将更改密钥。

图 8.26:编辑主机行,包含新服务器名称的 base64 编码值
  1. 接下来,我们还需要在wp-config.php文件中进行更改。为此,让我们exec进入当前的 WordPress 容器并更改该值:
kubectl exec -it <wordpress pod name> -n wordpress sh
apt update
apt install vim -y
vim /bitnami/wordpress/wp-config.php

这将再次打开vi编辑器。导航到包含DB_HOST配置行的第 32 行。按I进入插入模式,删除当前值,并用图 8.27中显示的恢复数据库的名称替换。然后按Esc,输入:wq!,然后按Enter。确保粘贴真实值,而不是base64编码的值:

在 vi 编辑器中,转到包含数据库主机名的第 32 行。将此名称替换为恢复数据库的名称。这将生成一个显示恢复数据库名称的输出屏幕。

图 8.27:将数据库名称更改为恢复的数据库

然后,使用以下命令退出 Pod:

exit

尽管我们现在已重置了密钥值和配置文件,但这并不意味着我们的服务器会自动获取新值。我们现在必须重新启动 Pod,以确保配置再次被读取。

  1. 有许多方法可以做到这一点,我们将删除现有的 Pod。一旦删除了这个 Pod,我们的ReplicaSet控制器将注意到这一点并创建一个新的 Pod。要删除 Pod,请使用以下命令:
kubectl delete pod <wordpress pod name> -n wordpress
  1. 几秒钟后,您应该看到正在创建一个新的 Pod。新的 Pod 上线需要 5 到 10 分钟。一旦上线,您可以观看该 Pod 的容器日志,并验证您确实连接到了新的数据库:
kubectl logs <new wordpress pod> -n wordpress

这应该包含如图 8.28所示的一行:

kubectl logs  -n wordpress 命令生成一个输出,显示连接到恢复数据库的 WordPress Pod 的日志。

图 8.28:显示连接到恢复数据库的 WordPress Pod 的日志

这表明我们现在已连接到我们恢复的数据库。我们可以确认实际内容已经恢复。您可以通过浏览到http://<EXTERNAL IP>来连接到 WordPress 网站本身:

单击连接到 WordPress 网站后,您将看到一个白屏,上面显示着一条消息,确认备份和恢复。这表明博客文章已成功恢复。

图 8.29:显示博客文章成功恢复的说明

在本节中,我们探讨了 Azure Database for MySQL 的备份和恢复能力。我们发布了一篇博客文章,并恢复了存储该博客文章的数据库。我们将我们的 WordPress 实例连接到恢复的数据库,并能够验证博客文章已成功恢复。

执行备份只是 Azure Database for MySQL 的能力之一。在下一节中,我们将探讨该服务的灾难恢复能力。

灾难恢复(DR)选项

根据您的应用程序要求和灾难恢复需求,您可以向 MySQL 服务器添加副本。副本可以在同一区域创建,以提高读取性能,也可以在辅助区域创建。

如果您正在为灾难恢复场景做准备,您需要在辅助区域设置一个副本。这将保护您免受 Azure 区域性故障的影响。设置这一点时,Azure 将会异步地将数据从主服务器复制到您设置的副本服务器。在复制进行时,副本服务器可以用于读取,但不能用于写入。如果发生灾难,意味着 Azure 区域发生了区域性故障,您需要停止复制,将副本服务器变成一个能够同时提供读取和写入请求的服务器。

在新区域创建副本非常简单。虽然设置和测试复制不在本书的范围内,但我们将展示如何设置。要配置复制,您需要在 MySQL 刀片中打开复制选项卡,如图 8.30所示:

要通过 Azure 门户创建副本,请单击导航窗格左侧的 MySQL 刀片中的复制选项卡。副本不显示任何结果。在屏幕右侧输入服务器名称和位置。这将显示每月成本。

图 8.30:通过 Azure 门户创建副本

注意

备份、恢复和复制选项的完整列表在docs.microsoft.com/azure/mysql/concepts-backupdocs.microsoft.com/azure/mysql/concepts-read-replicas中有文档记录。

在本节中,我们描述了 Azure Database for MySQL 复制到辅助区域的能力。这个副本可以用来为您的数据库构建 DR 策略。在下一节中,我们将介绍如何使用活动日志来审计谁对您的服务器进行了更改。

审查审计日志

数据库包含业务关键数据。您将希望有一个日志记录系统,可以显示谁对您的数据库进行了更改。

当您在 Kubernetes 集群上运行数据库时,如果出现问题,很难获取审计日志。您需要一种强大的动态设置审计级别的方式,具体取决于情况。您还必须确保日志被运送到集群外部。

Azure Database for MySQL 服务通过 Azure 门户提供了强大的审计机制来解决上述问题。该服务有两种不同的日志视图:

  • 活动日志:活动日志显示发生在数据库 Azure 对象上的所有更改。Azure 记录所有针对 Azure 资源的创建、更新和删除事务,并将这些日志保存 90 天。对于 MySQL 来说,这意味着对大小、备份和复制设置等的所有更改。这些日志对于确定谁对您的数据库进行了更改非常有用。

  • 服务器日志:服务器日志包括来自数据库中实际数据的日志。MySQL 有多个可配置的日志可用。通常建议打开审计日志以验证谁访问了您的数据库,并打开慢查询监视以跟踪运行缓慢的任何查询。

让我们一起看看这两种日志:

  1. 要访问活动日志,请在 Azure 门户中打开 MySQL 数据库刀片。在左侧导航中,寻找活动日志。这将打开活动日志视图,如图 8.31所示:单击 Azure 门户左侧窗格中的活动日志选项卡。您将看到针对相应 Azure 数据库执行的操作。
图 8.31:Azure 活动日志显示针对 Azure 数据库执行的操作

活动日志提供了非常有价值的信息,可以追溯已执行的活动。您应该在活动日志中找到指向您之前对连接安全设置所做更改的事件。

  1. 服务器日志可以通过在左侧导航中查找服务器日志来获取。服务器日志默认情况下未打开,如图 8.32所示:如果您在 Azure 门户的导航窗格左侧单击服务器日志选项卡,它将指示默认情况下没有服务器日志。
图 8.32:导航显示默认情况下没有服务器日志
  1. 让我们打开服务器日志。我们将通过启用log_slow...语句和slow_query_log来启用审计日志和性能监控,如图 8.33所示:一旦服务器参数页面打开,点击名称为 log_slow...语句和 slow_query_log 的参数。确保它们的值为 ON。
图 8.33:启用审计日志和慢查询日志

一旦您打开了这些日志,实际日志将需要几分钟时间才能显示出来。几分钟后,您应该在 Azure 门户中的服务器日志选项卡中看到日志,如图 8.34所示:

几分钟后,您可以通过单击 MySQL 刀片左侧导航窗格上的服务器日志选项卡来查看实际日志。

图 8.34:在 Azure 门户中显示服务器日志

让我们确保在部署后再次清理并将我们的集群缩减到两个节点。缩减到两个节点将确保您在 Azure 订阅上节省成本:

helm delete wp -n wordpress
helm delete osba -n osba
helm delete catalog -n catalog
kubectl delete ns wordpress osba catalog
az aks scale -n <clustername> -g <cluster resource group> -c 2

在本节中,我们介绍了 Azure 为 MySQL 数据库生成的两种日志类型。我们查看了活动日志,以查看针对 Azure 数据库执行了哪些操作,并打开了服务器日志,以了解数据库内部发生了什么。

总结

本章重点介绍了使用 WordPress 示例解决方案,该解决方案利用 MySQL 数据库作为数据存储。我们首先向您展示了如何通过安装 Azure 的 Open Service Broker 来设置集群,以连接 MySQL 数据库。然后,我们向您展示了如何设置 MySQL 数据库,并通过更改默认配置来大大减少数据库的攻击面,从而不允许公共访问数据库。接着,我们讨论了如何从备份中恢复数据库,以及如何利用解决方案进行灾难恢复。最后,我们讨论了如何配置审计日志以进行故障排除。

在下一章中,您将学习如何在 AKS 上实现微服务,包括使用事件中心实现应用程序之间的松耦合集成。

第九章:连接到 Azure 事件中心

基于事件的集成是实现微服务的关键模式。微服务架构的理念是将单体应用程序分解为一组较小的服务。事件通常用于协调这些不同的服务之间。当您考虑一个事件时,它可以是许多事物之一。金融交易可以是一个事件,同样的,IoT 传感器数据、网页点击和浏览等也可以是事件。

处理这些类型事件的常用软件是 Apache Kafka(简称 Kafka)。Kafka 最初由 LinkedIn 开发,后来捐赠给 Apache 软件基金会。它是一个流行的开源流平台。流平台具有三个核心能力:发布和订阅消息流(类似于队列),以持久方式存储这些流,并在发生时处理这些流。

Azure 有一个类似于 Apache Kafka 的服务,称为 Azure 事件中心。事件中心是一个提供实时数据摄入的托管服务。它易于设置和使用,并且可以动态扩展。事件中心还与其他 Azure 服务集成,如流分析、函数和 databricks。这些预构建的集成使您更容易构建从事件中心消费事件的应用程序。

事件中心还提供 Kafka 端点。这意味着您可以配置现有的基于 Kafka 的应用程序,并将其指向事件中心。使用事件中心来处理 Kafka 应用程序的好处是您不再需要管理 Kafka 集群,因为您可以将其作为托管服务来使用。

在本章中,您将学习如何在 AKS 上实现微服务,并使用事件中心在应用程序之间实现松耦合集成。您将部署一个使用 Kafka 发送事件的应用程序,并用 Azure 事件中心替换您自己的 Kafka 集群。正如您将在本章中学到的,基于事件的集成是单体和基于微服务的应用程序之间的关键区别之一。

  • 部署一组微服务

  • 使用 Azure 事件中心

我们将从部署一组构建社交网络的微服务开始本章。

部署一组微服务

在本节中,我们将部署一个来自名为社交网络的演示应用程序的一组微服务。该应用程序由两个主要的微服务组成:用户好友。用户服务将所有用户存储在自己的数据存储中。用户由 ID、名和姓表示。好友服务存储用户的好友。好友关系链接了两个好友的用户 ID,并且还有自己的 ID。

添加用户/添加好友的事件被发送到消息队列。该应用程序使用 Kafka 作为消息队列,用于存储与用户、好友和推荐相关的事件。

这个队列被一个推荐服务所消费。这个服务由一个Neo4j数据库支持,可以用来查询用户之间的关系。Neo4j 是一个流行的图形数据库平台。图形数据库不同于典型的关系数据库,如 MySQL。图形数据库专注于存储不同元素之间的关系。您可以用问题查询图形数据库,比如给我用户 X 和用户 Y 的共同好友

在数据流方面,您可以创建用户和友谊关系。创建用户或友谊关系将在消息队列上生成一条消息,这将导致数据在 Neo4j 数据库中填充。该应用程序没有 Web 界面。您主要将使用命令行与应用程序进行交互,尽管我们可以连接到 Neo4j 数据库以验证数据是否已填充到数据库中。

在接下来的部分,您将学习以下内容:

  • 使用 Helm 部署一个示例基于微服务的应用程序。

  • 通过发送事件并观察对象的创建和更新来测试服务。

让我们从部署应用程序开始。

使用 Helm 部署应用程序

在本节中,我们将使用 Helm 部署演示应用程序。这将使用本地 Kafka 实例部署完整的应用程序。应用程序部署后,我们将生成一个小型社交网络,并验证我们能够创建该社交网络。

  1. 这个示例有很多资源需求。为了满足这些需求,将您的集群扩展到四个节点:
az aks nodepool scale --node-count 4 -g rg-handsonaks \
  --cluster-name handsonaks --name agentpool
  1. 这个示例的代码已经包含在本书的 GitHub 存储库中。您可以在Chapter09文件夹下的social-network文件夹中找到代码。导航到这个文件夹:
cd Chapter09/social-network
  1. 要运行 Kafka,我们还需要运行ZooKeeper。ZooKeeper 是 Apache 基金会的另一个开源软件项目。它提供命名、配置管理、同步和分组服务的能力。我们将使用bitnami的 Kafka 和 ZooKeeper Helm 图表,因此让我们添加所需的 Helm 存储库:
helm repo add bitnami https://charts.bitnami.com
helm repo add incubator https://kubernetes-charts-incubator.storage.googleapis.com

这将生成如图 9.1所示的输出:

输出屏幕显示两个 Helm 存储库,bitnami 和 incubator,被添加到您的存储库中。

图 9.1:添加 Helm 存储库
  1. 让我们更新依赖项以使依赖图表可用:
helm dep update deployment/helm/social-network
helm dep update deployment/helm/friend-service
helm dep update deployment/helm/user-service
helm dep update deployment/helm/recommendation-service

这将显示类似于图 9.2的内容四次:

为了使依赖图表可用,输出屏幕将显示成功接收来自 svc-cat、incubator、azure、jetstack、bitnami 和 stable 等四个依赖项更新的单独消息。

图 9.2:更新依赖项

注意

在此示例中,您可能会看到类似于以下内容的警告:walk.go:74: found symbolic link in path:。这是一个可以安全忽略的警告。

  1. 接下来,为此应用程序创建一个新的namespace
kubectl create namespace social-network

这将生成如下输出:

使用 kubectl create namespace social-network 命令,创建一个新的命名空间。

图 9.3:创建一个新的命名空间
  1. 现在,继续部署应用程序:
helm install social-network --namespace social-network \
  --set fullNameOverride=social-network \
  --set edge-service.service.type=LoadBalancer \
  deployment/helm/social-network
  1. 使用以下命令检查部署中 Pod 的状态:
kubectl get pods -w -n social-network

正如您在图 9.4中所看到的,大约需要 5 分钟才能使所有的 Pod 都正常运行起来:

输出显示共有 20 个状态为 Running 的 Pod。

图 9.4:显示所有状态为 Running 的 Pod 的输出
  1. 应用程序成功部署后,您可以连接到边缘服务。要获取其 IP,请使用以下命令:
kubectl get service -n social-network

此命令将生成如下输出:

您可以使用 kubectl get service -n social-network 命令获取 edge-service 的外部 IP。

图 9.5:获取边缘服务的外部 IP
  1. 您可以进行两个测试来验证应用程序是否正常工作。测试 1 是在浏览器中连接端口9000上的边缘服务。图 9.6显示了 Whitelabel 错误页面,显示应用程序正在运行:Whitelabel 错误页面将显示应用程序没有配置错误视图,因此您看到这个作为后备。这表明应用程序正在运行。
图 9.6:Whitelabel 错误页面,显示应用程序正在运行
  1. 验证应用程序是否运行的第二个测试是实际生成一个小型社交网络。这将验证所有服务是否正常工作。您可以使用以下命令创建这个网络:
bash ./deployment/sbin/generate-serial.sh <external-ip>:9000

这个命令将生成大量输出。输出将以图 9.7中显示的元素开头:

在创建新的社交网络时,初始输出屏幕将显示名字、姓氏、创建日期和时间、上次修改日期和时间以及一个 ID。

图 9.7:创建新的社交网络时的初始输出
  1. 生成一个 15 人的网络大约需要一分钟的时间。要验证网络是否已成功创建,请在您的网络浏览器中浏览到http://<external-ip>:9000/user/v1/users/1。这应该会显示一个代表社交网络中用户的小 JSON 对象,如图 9.8所示:在用户服务中成功创建用户后,访问:9000/user/v1/users/1 的输出屏幕将显示用户的名字和姓氏、创建日期和时间以及上次修改日期和时间。
图 9.8:用户服务中成功创建用户
  1. 最后,您可以连接到 Neo4j 数据库并可视化您创建的社交网络。要能够连接到 Neo4j,您需要首先将其公开为一个服务。使用社交网络文件夹中的neo4j-service.yaml文件来公开它:
kubectl create -f neo4j-service.yaml -n social-network

然后,获取服务的公共 IP 地址。这可能需要大约一分钟才能使用:

kubectl get service neo4j-service -n social-network 

上述命令将生成以下输出:

输出显示了 Neo4j 服务的外部 IP 地址。

图 9.9:显示 Neo4j 服务的外部 IP 地址的输出

注意

请注意,Neo4j 服务的外部 IP 可能与边缘服务的外部 IP 不同。

  1. 使用浏览器连接到http://<external-ip>:7474。这将打开一个登录屏幕。使用以下信息登录:
  • 连接 URLbolt://<external-ip>:7678

  • 用户名neo4j

  • 密码neo4j

您的连接信息应该类似于图 9.10

要登录 Neo4j 服务,用户必须在连接 URL 字段中输入 bolt://:7687,将用户名设置为 neo4j,密码设置为 neo4j。然后他们需要点击密码字段下面的连接选项卡。这将帮助他们登录浏览器。

图 9.10:登录到 Neo4j 浏览器
  1. 一旦连接到 Neo4j 浏览器,您可以看到实际的社交网络。点击数据库信息图标,然后点击用户。这将生成一个查询,显示您刚刚创建的社交网络。这将类似于图 9.11社交网络的输出屏幕分为两部分。屏幕的左侧显示数据库信息,如节点标签、关系类型、属性键和数据库,而屏幕的其余部分显示用户(14)和朋友(149)的名称。输出还包含所创建的社交网络的图形表示。
图 9.11:您刚刚创建的社交网络的视图

在当前示例中,我们已经设置了端到端的应用程序,使用在我们的 Kubernetes 集群上运行的 Kafka 作为消息队列。在我们进入下一节之前,让我们删除该示例。要删除本地部署,请使用以下命令:

helm delete social-network -n social-network
kubectl delete pvc -n social-network --all
kubectl delete pv --all

在下一节中,我们将摆脱在集群中存储事件,并将它们存储在 Azure 事件中心。通过利用 Azure 事件中心上的本机 Kafka 支持,并切换到使用更适合生产的事件存储,我们将看到这个过程是简单的。

使用 Azure 事件中心

自己在集群上运行 Kafka 是可能的,但对于生产使用可能很难运行。在本节中,我们将把维护 Kafka 集群的责任转移到 Azure 事件中心。事件中心是一个完全托管的实时数据摄入服务。它原生支持 Kafka 协议,因此,通过轻微修改,我们可以将我们的应用程序从使用本地 Kafka 实例更新为可扩展的 Azure 事件中心实例。

在接下来的几节中,我们将执行以下操作:

  • 通过门户创建事件中心并收集连接我们基于微服务的应用程序所需的详细信息

  • 修改 Helm 图表以使用新创建的事件中心

让我们开始创建事件中心。

创建事件中心

在本节中,我们将创建 Azure 事件中心。稍后我们将使用此事件中心来流式传输新消息。执行以下步骤创建事件中心:

  1. 要在 Azure 门户上创建事件中心,请搜索事件中心,如图 9.12所示:在搜索栏选项卡中,用户需要输入“事件中心”以在 Azure 门户中创建事件中心。
图 9.12:在搜索栏中查找事件中心
  1. 点击事件中心

  2. 事件中心选项卡上,点击添加,如图 9.13所示:要添加新的事件中心,用户需要点击屏幕最左侧的+添加选项卡。

图 9.13:添加新的事件中心
  1. 填写以下细节:
  • 名称:此名称应是全局唯一的。考虑在名称中添加您的缩写。

  • 定价层:选择标准定价层。基本定价层不支持 Kafka。

  • 使此命名空间区域多余:已禁用。

  • Azure 订阅:选择与托管 Kubernetes 集群的订阅相同的订阅。

  • 资源组:选择我们为集群创建的资源组,在我们的情况下是rg-handsonaks

  • 位置:选择与您的集群相同的位置。在我们的情况下,这是West US 2

  • 吞吐量单位:1 个单位足以进行此测试。

  • 自动膨胀:已禁用。

这应该给您一个类似于图 9.14的创建视图:

有各种需要填写的字段。定价层应设置为标准,使此命名空间区域冗余选项应禁用,吞吐量单位应设置为 1,自动膨胀选项应禁用,并且名称、订阅、资源组和位置字段也需要填写。在这些字段的底部,您将看到创建选项卡。这就是您的事件中心创建的样子。

图 9.14:您的事件中心创建应该是这样的
  1. 点击向导底部的创建按钮来创建您的事件中心。

  2. 一旦事件中心创建完成,选择它,如图 9.15所示:一旦事件中心创建完成,您将被引导到一个窗口,在那里您可以在门户上查看事件中心。您需要通过点击事件中心的名称来选择它。

图 9.15:一旦创建,点击事件中心名称
  1. 点击共享访问策略,选择RootManageSharedAccessKey,并复制连接字符串-主密钥,如图 9.16所示:这个屏幕显示了获取事件中心连接字符串的过程。第一步是在左侧菜单中点击共享访问策略。第二步是选择 RootManageSharedAccessKey 策略,第三步是点击主连接字符串旁边的复制图标。
图 9.16:复制主连接字符串

使用 Azure 门户,我们已经创建了一个事件中心,可以存储和处理生成的事件。我们需要收集连接字符串,以便连接我们的基于微服务的应用程序。在下一节中,我们将重新部署我们的社交网络,并配置它连接到事件中心。为了能够部署我们的社交网络,我们将不得不对 Helm 图表进行一些更改,以便将我们的应用程序指向事件中心而不是 Kafka。

修改 Helm 文件

我们将把微服务部署从使用本地 Kafka 实例切换到使用 Azure 托管的、与 Kafka 兼容的事件中心实例。为了做出这个改变,我们将修改 Helm 图表,以使用事件中心而不是 Kafka:

  1. 修改values.yaml文件,以禁用集群中的 Kafka,并包括连接细节到您的事件中心:
code deployment/helm/social-network/values.yaml

确保更改以下值:

第 5、18、26 和 34 行:将其更改为enabled: false

第 20、28 和 36 行:将其更改为您的事件中心名称。

第 21、29 和 37 行:将其更改为您的事件中心连接字符串:

1   nameOverride: social-network
2   fullNameOverride: social-network
3
4   kafka:
5     enabled: true
6     nameOverride: kafka
7     fullnameOverride: kafka
8     persistence:
9       enabled: false
10    resources:
11      requests:
12        memory: 325Mi
13   
14   
15  friend-service:
16    fullnameOverride: friend-service
17    kafka:
18      enabled: true
19    eventhub:
20      name: <event hub name>
21      connection: "<event hub connection string>"
22   
23  recommendation-service:
24    fullnameOverride: recommendation-service
25    kafka:
26      enabled: true
27    eventhub:
28      name: <event hub name>
29      connection: "<event hub connection string>"
30
31  user-service:
32    fullnameOverride: user-service
33    kafka:
34      enabled: true
35    eventhub:
36      name: <event hub name>
37      connection: "<event hub connection string>"

注意

对于我们的演示,我们将连接字符串存储在 Helm 值文件中。这不是最佳实践。对于生产用例,您应该将这些值存储为秘密,并在部署中引用它们。我们将在第十章保护您的 AKS 集群中探讨这一点。

  1. 按照以下方式运行部署:
helm install social-network deployment/helm/social-network/ -n social-network --set edge-service.service.type=LoadBalancer
  1. 等待所有的 Pod 启动。您可以使用以下命令验证所有的 Pod 是否已经启动并正在运行:
kubectl get pods -n social-network

这将生成以下输出:

屏幕上显示的 14 个 Pod 显示它们的运行状态为 Running。

图 9.17:输出显示所有 Pod 的运行状态
  1. 要验证您是否连接到事件中心,而不是本地 Kafka,您可以在门户中检查事件中心,并检查不同的主题。您应该会看到一个 friend 和一个 user 主题,如图 9.18所示:当您在屏幕左侧的菜单中滚动时,您会看到实体选项卡。点击其中的事件中心。您将看到在您的事件中心中创建的两个主题。这些主题的名称应该是 friend 和 user。
图 9.18:显示在您的事件中心创建的两个主题
  1. 继续观察 Pod。当所有的 Pod 都已启动并正在运行时,获取边缘服务的外部 IP。您可以使用以下命令获取该 IP:
kubectl get svc -n social-network
  1. 然后,运行以下命令验证实际社交网络的创建:
bash ./deployment/sbin/generate-serial.sh <external-ip>:9000

这将再次创建一个包含 15 个用户的社交网络,但现在将使用事件中心来发送所有与用户、好友和推荐相关的事件。

  1. 您可以在 Azure 门户上看到这些活动。Azure 门户为事件中心创建了详细的监控图表。要访问这些图表,请点击friend事件中心,如图 9.19所示:在屏幕左侧的导航窗格中,向下滚动到实体部分。点击事件中心。您会看到两个主题:friend 和 user。点击 friend 主题以获取更多指标。
图 9.19:点击 friend 主题以获取更多指标

图 9.20中,您可以看到 Azure 门户为您提供了三个图表:请求的数量、消息的数量和总吞吐量:

Azure 门户显示了主题的三个图表。这些高级图表提供了请求的数量,消息的数量和总吞吐量。这些图表的底部都有一个蓝色图标,表示传入请求,传入消息和传入字节。您还将看到一个橙色图标,表示成功的请求,传出消息和传出字节。图片中的图表显示了上升的尖峰。

图 9.20:默认情况下显示高级图表

您可以进一步深入研究各个图表。例如,单击消息图表。这将带您进入 Azure 监视器中的交互式图表编辑器。您可以按分钟查看事件中心的进出消息数量,如图 9.21所示:

单击第二个图表,表示消息数量,您将看到更多详细信息。除了蓝色和橙色图标外,您还将看到靛蓝色和水绿色图标,分别表示捕获的消息和捕获积压(总和)。

图 9.21:单击图表以获取更多详细信息

让我们确保清理我们刚刚创建的部署,并将我们的集群缩减回去:

helm delete social-network -n social-network
kubectl delete pvc -n social-network --all
kubectl delete pv --all
kubectl delete service neo4j-service -n social-network
az aks nodepool scale --node-count 2 -g rg-handsonaks \
  --cluster-name handsonaks --name agentpool

您还可以在 Azure 门户中删除事件中心。要删除事件中心,请转到事件中心的概述页面,并选择删除按钮,如图 9.22所示。系统会要求您重复事件中心的名称,以确保您不会意外删除它:

单击左侧屏幕上的导航窗格中的概述。您将看到您创建的事件中心的详细信息。要删除此事件中心,请单击工具栏中的删除按钮。此按钮位于刷新按钮的左侧。

图 9.22:单击删除按钮以删除您的事件中心

本章结束了我们使用 Azure Kubernetes 服务与事件中心的示例。在这个示例中,我们重新配置了一个 Kafka 应用程序,以使用 Azure 事件中心。

摘要

在本章中,我们部署了一个基于微服务的应用程序,连接到 Kafka。我们使用 Helm 部署了这个示例应用程序。我们能够通过向本地创建的 Kafka 集群发送事件并观察创建和更新的对象来测试应用程序。

最后,我们介绍了使用 Kafka 支持在 Azure 事件中心存储事件,并且我们能够收集所需的细节来连接我们基于微服务的应用程序并修改 Helm 图表。

下一章将涵盖集群安全性。我们将涵盖 RBAC 安全性、秘钥管理以及使用 Istio 的网络安全。

第十章:保护您的 AKS 集群

“泄露机密会导致失败”是一个描述在 Kubernetes 管理的集群中很容易危及安全性的短语(顺便说一句,Kubernetes在希腊语中是舵手的意思,就像的舵手)。如果您的集群开放了错误的端口或服务,或者在应用程序定义中使用了明文作为秘密,不良行为者可以利用这种疏忽的安全性做几乎任何他们想做的事情。

在本章中,我们将更深入地探讨 Kubernetes 安全性。您将了解 Kubernetes 中的基于角色的访问控制(RBAC)的概念。之后,您将学习有关秘密以及如何使用它们的内容。您将首先在 Kubernetes 中创建秘密,然后创建一个 Key Vault 来更安全地存储秘密。最后,您将简要介绍服务网格概念,并且将给出一个实际示例供您参考。

本章将简要介绍以下主题:

  • 基于角色的访问控制

  • 设置秘密管理

  • 在 Key Vault 中使用存储的秘密

  • Istio 服务网格为您服务

注意

要完成有关 RBAC 的示例,您需要访问具有全局管理员权限的 Azure AD 实例。

让我们从 RBAC 开始这一章。

基于角色的访问控制

在生产系统中,您需要允许不同用户对某些资源有不同级别的访问权限;这被称为基于角色的访问控制RBAC)。本节将带您了解如何在 AKS 中配置 RBAC,以及如何分配不同权限的不同角色。建立 RBAC 的好处在于,它不仅可以防止意外删除关键资源,还是一项重要的安全功能,限制了对集群的完全访问权限。在启用 RBAC 的集群上,用户将能够观察到他们只能修改他们有权限访问的资源。

到目前为止,使用 Cloud Shell,我们一直在扮演root,这使我们可以在集群中做任何事情。对于生产用例来说,root 访问是危险的,应尽可能受到限制。通常公认的最佳实践是使用最小权限原则(PoLP)登录任何计算机系统。这可以防止对安全数据的访问和通过删除关键资源而造成意外停机。据统计,22%至 29%(blog.storagecraft.com/data-loss-statistics-infographic/)的数据丢失归因于人为错误。您不希望成为这一统计数字的一部分。

Kubernetes 开发人员意识到这是一个问题,并添加了 RBAC 以及服务角色的概念来控制对集群的访问。Kubernetes RBAC 有三个重要的概念:

  • 角色:角色包含一组权限。角色默认没有权限,每个权限都需要明确声明。权限的示例包括getwatchlist。角色还包含这些权限所赋予的资源。资源可以是所有 Pod、部署等,也可以是特定对象(如pod/mypod)。

  • 主体:主体可以是分配了角色的 Azure AD 用户或组。

  • RoleBinding:RoleBinding 将一个主体与特定命名空间中的角色或者 ClusterRoleBinding 中的整个集群中的角色进行了关联。

一个重要的概念要理解的是,在与 AKS 进行交互时,有两个 RBAC 层次:Azure RBAC 和 Kubernetes RBAC。Azure RBAC 处理分配给人们在 Azure 中进行更改的角色,比如创建、修改和删除集群。Kubernetes RBAC 处理集群中资源的访问权限。两者都是独立的控制平面,但可以使用源自 Azure AD 的相同用户和组。

一个架构图,显示了 RBAC 的两个层次:Azure RBAC 和 Kubernetes RBAC。在 Azure 中,Azure AD 用户或组被分配 Azure 角色,以允许访问订阅和资源组中的节点。同样,Azure AD 用户或组被分配 Kubernetes 角色,以访问 Kubernetes 中的 Pod、部署和命名空间。

图 10.1:两个不同的 RBAC 平面,Azure 和 Kubernetes

Kubernetes 中的 RBAC 是一个可选功能。在 AKS 中,默认情况下是创建启用了 RBAC 的集群。但是,默认情况下,集群未集成到 Azure AD 中。这意味着默认情况下,您无法授予 Kubernetes 权限给 Azure AD 用户。在这个例子中,我们将创建一个与 Azure AD 集成的新集群。让我们通过在 Azure AD 中创建一个新用户和一个新组来开始我们对 RBAC 的探索。

创建一个集成了 Azure AD 的新集群

在本节中,我们将创建一个与 Azure AD 集成的新集群。这是必需的,这样我们就可以在接下来的步骤中引用 Azure AD 中的用户。所有这些步骤将在 Cloud Shell 中执行。我们还提供了一个名为cluster-aad.sh的文件中的步骤。如果您希望执行该脚本,请更改前四行中的变量以反映您的偏好。让我们继续执行这些步骤:

  1. 我们将从缩减当前集群到一个节点开始:
az aks nodepool scale --cluster-name handsonaks \
  -g rg-handsonaks --name agentpool--node-count 1
  1. 然后,我们将设置一些在脚本中将使用的变量:
EXISTINGAKSNAME="handsonaks"
NEWAKSNAME="handsonaks-aad"
RGNAME="rg-handsonaks"
LOCATION="westus2"
TENANTID=$(az account show --query tenantId -o tsv)
  1. 现在,我们将从我们的 AKS 集群中获取现有的服务主体。我们将重用此服务主体,以授予新集群访问我们的 Azure 订阅的权限:
# Get SP from existing cluster and create new password
RBACSP=$(azaks show -n $EXISTINGAKSNAME -g $RGNAME \
  --query servicePrincipalProfile.clientId -o tsv)
RBACSPPASSWD=$(openssl rand -base64 32)
az ad sp credential reset --name $RBACSP \
  --password $RBACSPPASSWD --append
  1. 接下来,我们将创建一个新的 Azure AD 应用程序。这个 Azure AD 应用程序将用于获取用户的 Azure AD 组成员资格:
serverApplicationId=$(az ad app create \
    --display-name "${NEWAKSNAME}Server" \
    --identifier-uris "https://${NEWAKSNAME}Server" \
    --query appId -o tsv)
  1. 在下一步中,我们将更新应用程序,创建服务主体,并从服务主体获取密钥:
az ad app update --id $serverApplicationId --set groupMembershipClaims=All
az ad sp create --id $serverApplicationId
serverApplicationSecret=$(az ad sp credential reset \
    --name $serverApplicationId \
    --credential-description "AKSPassword" \
    --query password -o tsv)
  1. 然后,我们将授予此服务主体访问 Azure AD 中的目录数据的权限:
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
az ad app permission grant --id $serverApplicationId\
    --api 00000003-0000-0000-c000-000000000000
  1. 这里有一个手动步骤,需要我们转到 Azure 门户。我们需要授予应用程序管理员同意。为了实现这一点,在 Azure 搜索栏中查找Azure Active Directory在 Azure 搜索栏中搜索 Azure Active Directory。
图 10.2:在搜索栏中查找 Azure Active Directory
  1. 然后,在左侧菜单中选择应用程序注册在左侧菜单中选择应用程序注册选项卡。
图 10.3:选择应用程序注册
  1. 应用程序注册中,转到所有应用程序,查找Server,并选择该应用程序:查找我们之前使用脚本创建的名为 handsonaksaadserver 的应用程序注册。
图 10.4:查找我们之前使用脚本创建的应用程序注册
  1. 在该应用的视图中,点击API 权限,然后点击为默认目录授予管理员同意(此名称可能取决于您的 Azure AD 名称):转到左侧菜单中的 API 权限,并点击按钮授予管理员特权。
图 10.5:授予管理员同意

在接下来的提示中,选择以授予这些权限。

注意

授予管理员同意按钮可能需要大约一分钟才能激活。如果还没有激活,请等待一分钟然后重试。您需要在 Azure AD 中拥有管理员权限才能授予此同意。

  1. 接下来,我们将创建另一个服务主体并授予其权限。这个服务主体将接受用户的认证请求,并验证他们的凭据和权限:
clientApplicationId=$(az ad app create \
    --display-name "${NEWAKSNAME}Client" \
    --native-app \
    --reply-urls "https://${NEWAKSNAME}Client" \
    --query appId -o tsv)
az ad sp create --id $clientApplicationId
oAuthPermissionId=$(az ad app show --id $serverApplicationId\
--query "oauth2Permissions[0].id" -o tsv)
az ad app permission add --id $clientApplicationId \
--api$serverApplicationId --api-permissions \
$oAuthPermissionId=Scope
az ad app permission grant --id $clientApplicationId\
--api $serverApplicationId
  1. 然后,作为最后一步,我们可以创建新的集群:
azaks create \
    --resource-group $RGNAME \
    --name $NEWAKSNAME \
    --location $LOCATION
    --node-count 2 \
    --node-vm-size Standard_D1_v2 \
    --generate-ssh-keys \
    --aad-server-app-id $serverApplicationId \
    --aad-server-app-secret $serverApplicationSecret \
    --aad-client-app-id $clientApplicationId \
    --aad-tenant-id $TENANTID \
    --service-principal $RBACSP \
    --client-secret $RBACSPPASSWD

在本节中,我们已经创建了一个集成了 Azure AD 的新 AKS 集群,用于 RBAC。创建一个新集群大约需要 5 到 10 分钟。在新集群正在创建时,您可以继续下一节并在 Azure AD 中创建新用户和组。

在 Azure AD 中创建用户和组

在本节中,我们将在 Azure AD 中创建一个新用户和一个新组。我们将在本章后面使用它们来分配权限给我们的 AKS 集群。

注意

您需要在 Azure AD 中拥有用户管理员角色才能创建用户和组。

  1. 首先,在 Azure 搜索栏中查找Azure 活动目录在 Azure 搜索栏中搜索 Azure 活动目录。
图 10.6:在搜索栏中查找 Azure 活动目录
  1. 点击左侧的用户。然后选择新用户来创建一个新用户:导航到左侧菜单中的所有用户选项,然后点击新用户按钮。
图 10.7:点击新用户以创建新用户
  1. 提供有关用户的信息,包括用户名。确保记下密码,因为这将需要用于登录:在新用户窗口中,添加所有用户详细信息并确保记下密码。
图 10.8:提供用户详细信息(确保记下密码)
  1. 创建用户后,返回 Azure AD 刀片并选择。然后点击新建组按钮创建一个新组:在左侧菜单中选择所有组选项卡,然后点击新建组按钮创建一个新组。
图 10.9:点击新建组创建新组
  1. 创建一个新的安全组。将组命名为kubernetes-admins,并将Tim添加为组的成员。然后点击底部的创建按钮:在新组窗口中,将组类型设置为安全,添加组名称和组描述,并将我们在上一步中创建的用户添加到该组中。
图 10.10:添加组类型、组名称和组描述
  1. 我们现在已经创建了一个新用户和一个新组。作为最后一步,我们将使该用户成为 AKS 中的集群所有者,以便他们可以使用 Azure CLI 访问集群。为此,在 Azure 搜索栏中搜索您的集群:在 Azure 搜索栏中输入集群名称并选择该集群。
图 10.11:在 Azure 搜索栏中查找您的集群
  1. 在集群刀片中,点击访问控制(IAM),然后点击添加按钮添加新的角色分配。选择Azure Kubernetes Service Cluster User Role并分配给您刚创建的新用户:在集群刀片中,选择访问控制(IAM),点击屏幕顶部的添加按钮,选择 Azure Kubernetes Service Cluster User Role,并查找我们之前创建的用户。
图 10.12:为新创建的用户分配集群用户角色
  1. 由于我们还将为新用户使用 Cloud Shell,因此我们将为他们提供对 Cloud Shell 存储账户的贡献者访问权限。首先,在 Azure 搜索栏中搜索存储在 Azure 搜索栏中搜索存储账户。
图 10.13:在 Azure 搜索栏中搜索存储账户
  1. 选择此存储账户所在的资源组:在存储账户窗口中,选择由 Cloud Shell 创建的存储账户所在的资源组。
图 10.14:选择资源组
  1. 转到访问控制(IAM),然后单击添加按钮。将Storage Account Contributor角色授予您新创建的用户:导航到访问控制(IAM)窗口,然后单击添加按钮。然后,将 Contributor 角色授予我们新创建的用户。
图 10.15:给予新创建的用户 Storage Account Contributor 访问权限

这已经完成了创建新用户和组,并给予该用户对 AKS 的访问权限。在下一节中,我们将为该用户和组配置 RBAC。

在 AKS 中配置 RBAC

为了演示 AKS 中的 RBAC,我们将创建两个命名空间,并在每个命名空间中部署 Azure Vote 应用程序。我们将给予我们的组对 Pod 的全局只读访问权限,并且我们将给予用户仅在一个命名空间中删除 Pod 的能力。实际上,我们需要在 Kubernetes 中创建以下对象:

  • ClusterRole来给予只读访问权限

  • ClusterRoleBinding来授予我们的组对该角色的访问权限

  • Role来在delete-access命名空间中给予删除权限

  • RoleBinding来授予我们的用户对该角色的访问权限我们将要构建的演示的图形表示。我们创建的组将获得 ReadOnlyClusterRole,我们创建的用户在 delete-access 命名空间中获得一个角色,以授予删除 pod 的权限。

图 10.16:组获得对整个集群的只读访问权限,用户获得对 delete-access 命名空间的删除权限

让我们在我们的集群上设置不同的角色:

  1. 要开始我们的示例,我们需要检索组的 ID。以下命令将检索组 ID:
az ad group show -g 'kubernetes-admins' --query objectId -o tsv

这将显示您的组 ID。记下来,因为我们在下一步中会需要它:

az ad group show 命令的输出,显示组 ID。

图 10.17:获取组 ID
  1. 由于我们为这个示例创建了一个新的集群,我们将获取凭据以登录到这个集群。我们将使用管理员凭据进行初始设置:
az aks get-credentials -n handsonaksad -g rg-handsonaks --admin
  1. 在 Kubernetes 中,我们将为这个示例创建两个命名空间:
kubectl create ns no-access
kubectl create ns delete-access
  1. 我们将在两个命名空间中部署azure-vote应用程序:
kubectl create -f azure-vote.yaml -n no-access
kubectl create -f azure-vote.yaml -n delete-access
  1. 接下来,我们将创建ClusterRole文件。这在clusterRole.yaml文件中提供:
1   apiVersion: rbac.authorization.k8s.io/v1
2   kind: ClusterRole
3   metadata:
4     name: readOnly
5   rules:
6   - apiGroups: [""]
7     resources: ["pods"]
8     verbs: ["get", "watch", "list"]

让我们仔细看看这个文件:

第 2 行:定义了ClusterRole的创建

第 4 行:为我们的ClusterRole命名

第 6 行:给予所有 API 组的访问权限

第 7 行:给予所有 Pod 的访问权限

第 8 行:允许执行getwatchlist操作

我们将使用以下命令创建这个ClusterRole

kubectl create -f clusterRole.yaml
  1. 下一步是创建一个 ClusterRoleBinding。该绑定将角色链接到用户。这在clusterRoleBinding.yaml文件中提供:
1   apiVersion: rbac.authorization.k8s.io/v1
2   kind: ClusterRoleBinding
3   metadata:
4     name: readOnlyBinding
5   roleRef:
6     kind: ClusterRole
7     name: readOnly
8     apiGroup: rbac.authorization.k8s.io
9   subjects:
10  - kind: Group
11   apiGroup: rbac.authorization.k8s.io
12   name: "<group-id>"

让我们仔细看看这个文件:

第 2 行:定义我们正在创建一个ClusterRoleBinding

第 4 行:为我们的ClusterRoleBinding命名

第 5-8 行:指的是我们在上一步中创建的ClusterRole

第 9-12 行:在 Azure AD 中引用我们的组

我们可以使用以下命令创建这个ClusterRoleBinding

kubectl create -f clusterRoleBinding.yaml
  1. 接下来,我们将创建一个限制在delete-access命名空间的Role。这在role.yaml文件中提供:
1   apiVersion: rbac.authorization.k8s.io/v1
2   kind: Role
3   metadata:
4     name: deleteRole
5     namespace: delete-access
6   rules:
7   - apiGroups: [""]
8     resources: ["pods"]
9     verbs: ["delete"]

这个文件类似于之前的ClusterRole文件。有两个有意义的区别:

第 2 行:定义我们正在创建一个Role,而不是ClusterRole

第 5 行:定义了在哪个命名空间中创建这个Role

我们可以使用以下命令创建这个Role

kubectl create -f role.yaml
  1. 最后,我们将创建将我们的用户链接到命名空间角色的RoleBinding。这在roleBinding.yaml文件中提供:
1   apiVersion: rbac.authorization.k8s.io/v1
2   kind: RoleBinding
3   metadata:
4     name: deleteBinding
5     namespace: delete-access
6   roleRef:
7     kind: Role
8     name: deleteRole
9     apiGroup: rbac.authorization.k8s.io
10  subjects:
11  - kind: User
12    apiGroup: rbac.authorization.k8s.io
13    name: "<user e-mail address>"

这个文件类似于之前的 ClusterRoleBinding 文件。有一些有意义的区别:

第 2 行:定义了创建一个RoleBinding而不是ClusterRoleBinding

第 5 行:定义了在哪个命名空间中创建这个RoleBinding

第 7 行:指的是一个普通的Role而不是ClusterRole

第 11-13 行:定义了我们的用户而不是一个组

我们可以使用以下命令创建这个RoleBinding

kubectl create -f roleBinding.yaml

这已经满足了 RBAC 的要求。我们已经创建了两个角色并设置了两个 RoleBindings。在下一节中,我们将通过以我们的用户身份登录到集群来探索 RBAC 的影响。

验证 RBAC

为了验证 RBAC 是否按预期工作,我们将使用新创建的用户登录到 Azure 门户。在新的浏览器或 InPrivate 窗口中转到portal.azure.com,并使用新创建的用户登录。您将立即收到更改密码的提示。这是 Azure AD 中的安全功能,以确保只有该用户知道他们的密码:

提示更改密码的浏览器窗口。

图 10.18:您将被要求更改密码

一旦我们更改了您的密码,我们就可以开始测试不同的 RBAC 角色:

  1. 我们将通过为新用户设置 Cloud Shell 来开始我们的实验。启动 Cloud Shell 并选择 Bash:选择 Bash 选项作为 Cloud Shell。
图 10.19:选择 Bash 作为 Cloud Shell
  1. 在下一个视图中,选择显示高级设置在导航到 bash 选项后,选择显示高级设置按钮。
图 10.20:选择显示高级设置
  1. 然后,将 Cloud Shell 指向现有的存储账户并创建一个新的文件共享:通过单击创建存储按钮,将 Cloud Shell 指向现有的存储账户以创建新的文件共享。
图 10.21:指向现有的存储账户并创建一个新的文件共享
  1. 一旦 Cloud Shell 可用,让我们获取连接到我们的 AKS 集群的凭据:
az aks get-credentials -n handsonaksaad -g rg-handsonaks
  1. 然后,我们将尝试在 kubectl 中执行一个命令。让我们尝试获取集群中的节点:
kubectl get nodes

由于这是针对启用 RBAC 的集群执行的第一个命令,您将被要求重新登录。浏览至microsoft.com/devicelogin并提供 Cloud Shell 显示给您的代码。确保您在此处使用新用户的凭据登录:

在提示窗口中输入 Cloud Shell 提供的代码,然后单击下一步按钮。

图 10.22:在提示中复制粘贴 Cloud Shell 显示给您的代码

登录后,您应该从 kubectl 收到一个Forbidden错误消息,通知您您没有权限查看集群中的节点。这是预期的,因为用户只被配置为可以访问 Pods:

kubectl 给出错误消息,并声明我们没有权限查看集群中的节点。

图 10.23:提示您登录和被禁止的消息
  1. 现在我们可以验证我们的用户可以查看所有命名空间中的 Pods,并且用户有权限在delete-access命名空间中删除 Pods:
kubectl get pods -n no-access
kubectl get pods -n delete-access

这应该对两个命名空间都成功。这是由于为用户组配置的ClusterRole

输出显示我们的用户可以查看两个命名空间中的 Pods。

图 10.24:我们的用户可以查看两个命名空间中的 Pods
  1. 让我们也验证一下“删除”权限:
kubectl delete pod --all -n no-access
kubectl delete pod --all -n delete-access

正如预期的那样,在no-access命名空间中被拒绝,在delete-access命名空间中被允许,如图 10.25所示:

验证删除权限显示在无访问命名空间中被拒绝,在删除访问命名空间中被允许。

图 10.25: 在无访问命名空间中被拒绝,在删除访问命名空间中被允许

在本节中,我们已经设置了一个集成了 Azure AD 的新集群,并验证了与 Azure AD 身份的 RBAC 的正确配置。让我们清理本节中创建的资源,获取现有集群的凭据,并将我们的常规集群缩减到两个节点:

az aks delete -n handsonaksaad -g rg-handsonaks
az aks get-credentials -n handsonaks -g rg-handsonaks
az aks nodepool scale --cluster-name handsonaks \
  -g rg-handsonaks --name agentpool --node-count 2

在下一节中,我们将继续探讨 Kubernetes 安全性的路径,这次是调查 Kubernetes 密码。

设置密码管理

所有生产应用程序都需要一些秘密信息才能运行。Kubernetes 具有可插拔的密码后端来管理这些密码。Kubernetes 还提供了多种在部署中使用密码的方式。管理密码并正确使用密码后端的能力将使您的服务能够抵抗攻击。

在之前的章节中,我们在一些部署中使用了密码。大多数情况下,我们将密码作为某种变量的字符串传递,或者 Helm 负责为我们创建密码。在 Kubernetes 中,密码是一种资源,就像 Pods 和 ReplicaSets 一样。密码始终与特定的命名空间相关联。必须在要使用它们的所有命名空间中创建密码。在本节中,我们将学习如何创建、解码和使用我们自己的密码。我们将首先使用 Kubernetes 中的内置密码,最后利用 Azure Key Vault 来存储密码。

创建您自己的密码

Kubernetes 提供了三种创建密码的方式,如下所示:

  • 从文件创建密码

  • 从 YAML 或 JSON 定义创建密码

  • 从命令行创建密码

使用上述任何方法,您可以创建三种类型的密码:

  • 通用密码: 这些可以使用文字值创建。

  • Docker-registry 凭据: 用于从私有注册表中拉取镜像。

  • TLS 证书: 用于存储 SSL 证书。

我们将从使用文件方法创建密码开始。

从文件创建密码

假设您需要存储用于访问 API 的 URL 和秘密令牌。为了实现这一点,您需要按照以下步骤进行操作:

  1. 将 URL 存储在apiurl.txt中,如下所示:
echo https://my-secret-url-location.topsecret.com \
> secreturl.txt
  1. 将令牌存储在另一个文件中,如下所示:
echo 'superSecretToken' > secrettoken.txt
  1. 让 Kubernetes 从文件中创建密码,如下所示:
kubectl create secret generic myapi-url-token \
--from-file=./secreturl.txt --from-file=./secrettoken.txt

在这个命令中,我们将秘密类型指定为generic

该命令应返回以下输出:

secret/myapi-url-token created
  1. 我们可以使用get命令检查秘密是否与任何其他 Kubernetes 资源以相同的方式创建:
kubectl get secrets

此命令将返回类似于图 10.26的输出:

kubectl get secrets 命令的输出显示了秘密的名称、类型、数据和年龄。输出中有一个高亮显示,突出显示了名为 myapi-url-token 的秘密,类型为 Opaque。

图 10.26:kubectl get secrets 的输出

在这里,您将看到我们刚刚创建的秘密,以及仍然存在于default命名空间中的任何其他秘密。我们的秘密是Opaque类型,这意味着从 Kubernetes 的角度来看,内容的模式是未知的。它是一个任意的键值对,没有约束,与 Docker 注册表或 TLS 秘密相反,后者具有将被验证为具有所需详细信息的模式。

  1. 要了解有关秘密的更多详细信息,您还可以运行describe命令:
kubectl describe secrets/myapi-url-token

您将获得类似于图 10.27的输出:

描述命令的输出显示了有关秘密的其他详细信息。

图 10.27:描述秘密的输出

正如您所看到的,前面的命令都没有显示实际的秘密值。

  1. 要获取秘密,请运行以下命令:
kubectl get -o yaml secrets/myapi-url-token

您将获得类似于图 10.28的输出:

通过 kubectl get secret 命令中的-o yaml 开关显示秘密的编码值的输出。

图 10.28:在 kubectl get secret 中使用-o yaml 开关可以获取秘密的编码值

数据以键值对的形式存储,文件名作为键,文件的 base64 编码内容作为值。

  1. 前面的值是 base64 编码的。Base64 编码并不安全。它会使秘密变得难以被操作员轻松阅读,但任何坏人都可以轻松解码 base64 编码的秘密。要获取实际值,请运行以下命令:
echo 'c3VwZXJTZWNyZXRUb2tlbgo=' | base64 -d

您将获得最初输入的值:

获取最初使用 echo  | base64 -d 命令输入的秘密的实际值。

图 10.29:Base64 编码的秘密可以轻松解码
  1. 同样,对于url值,您可以运行以下命令:
echo 'aHR0cHM6Ly9teS1zZWNyZXQtdXJsLWxvY2F0aW9uLnRvcHNlY3JldC5jb20K'| base64 -d

您将获得最初输入的url值,如图 10.30所示:

显示编码的 URL 的输出,这是最初输入的 URL。

图 10.30:编码的 URL 也可以很容易地解码

在本节中,我们能够使用文件对 URL 进行编码,并获取实际的秘密值。让我们继续探索第二种方法——从 YAML 或 JSON 定义创建秘密。

使用 YAML 文件手动创建秘密

我们将按照以下步骤使用 YAML 文件创建相同的秘密:

  1. 首先,我们需要将秘密编码为base64,如下所示:
echo 'superSecretToken' | base64

您将获得以下价值:

c3VwZXJTZWNyZXRUb2tlbgo=

您可能会注意到,这与我们在上一节中获取秘密的yaml定义时存在的值相同。

  1. 同样,对于url值,我们可以获取 base64 编码的值,如下面的代码块所示:
echo 'https://my-secret-url-location.topsecret.com' | base64

这将给您base64编码的 URL:

aHR0cHM6Ly9teS1zZWNyZXQtdXJsLWxvY2F0aW9uLnRvcHNlY3JldC5jb20K
  1. 现在我们可以手动创建秘密定义;然后保存文件。该文件已在代码包中提供,名称为myfirstsecret.yaml
1   apiVersion: v1
2   kind: Secret
3   metadata:
4     name: myapiurltoken-yaml
5   type: Opaque
6   data:
7     url: aHR0cHM6Ly9teS1zZWNyZXQtdXJsLWxvY2F0aW9uLnRvcHNlY3JldC5jb20K
8     token: c3VwZXJTZWNyZXRUb2tlbgo=

让我们调查一下这个文件:

第 2 行:这指定了我们正在创建一个秘密。

第 5 行:这指定了我们正在创建一个Opaque秘密,这意味着从 Kubernetes 的角度来看,值是无约束的键值对。

第 7-8 行:这些是我们秘密的 base64 编码值。

  1. 现在,我们可以像任何其他 Kubernetes 资源一样使用create命令创建秘密:
kubectl create -f myfirstsecret.yaml
  1. 我们可以通过以下方式验证我们的秘密是否已成功创建:
kubectl get secrets

这将显示一个类似于图 10.31的输出:

验证我们的秘密是否已成功使用 kubectl get secrets 命令创建的输出。

图 10.31:我们的秘密已成功从 YAML 文件创建
  1. 您可以通过在上一节中描述的方式使用kubectl get -o yaml secrets/myapiurltoken-yaml来双重检查秘密是否相同。

使用文字创建通用秘密

创建秘密的第三种方法是使用literal方法,这意味着您可以在命令行上传递值。要做到这一点,请运行以下命令:

kubectl create secret generic myapiurltoken-literal \
--from-literal=token='superSecretToken' \
--from-literal=url=https://my-secret-url-location.topsecret.com

我们可以通过运行以下命令来验证秘密是否已创建:

kubectl get secrets

这将给我们一个类似于图 10.32的输出:

运行 kubectl get secrets 命令的输出,验证我们的秘密是否已成功从 CLI 创建。

图 10.32:我们的秘密已成功从 CLI 创建

因此,我们已经使用文字值创建了秘密,除了前面两种方法。

创建 Docker 注册表密钥

在生产环境中连接到私有 Docker 注册表是必要的。由于这种用例非常常见,Kubernetes 提供了创建连接的机制:

kubectl create secret docker-registry <secret-name> \
--docker-server=<your- registry-server> \
--docker-username=<your-name> \
--docker-password=<your-pword> --docker-email=<your-email>

第一个参数是秘密类型,即docker-registry。然后,您给秘密一个名称;例如,regcred。其他参数是 Docker 服务器(index.docker.io/v1/用于 Docker Hub),您的用户名,您的密码和您的电子邮件。

您可以使用kubectl访问秘密以相同的方式检索秘密。

在 Azure 中,Azure 容器注册表ACR)最常用于存储容器映像。集群可以连接到 ACR 的方式有两种。第一种方式是在集群中使用像我们刚刚描述的秘密。第二种方式 - 这是推荐的方式 - 是使用服务主体。我们将在第十一章,无服务器函数中介绍集成 AKS 和 ACR。

Kubernetes 中的最终秘密类型是 TLS 秘密。

创建 TLS 秘密

TLS 秘密用于存储 TLS 证书。要创建可用于 Ingress 定义的 TLS 秘密,我们使用以下命令:

kubectl create secret tls <secret-name> --key <ssl.key> --cert <ssl.crt>

第一个参数是tls,用于设置秘密类型,然后是key值和实际的证书值。这些文件通常来自您的证书注册商。

注意

我们在第六章管理您的 AKS,集群中创建了 TLS 秘密,在那里我们使用cert-manager代表我们创建了这些秘密。

如果您想生成自己的秘密,可以运行以下命令生成自签名 SSL 证书:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 - keyout /tmp/ssl.key -out /tmp/ssl.crt -subj "/CN=foo.bar.com"

在本节中,我们介绍了 Kubernetes 中不同的秘密类型,并看到了如何创建秘密。在下一节中,我们将在我们的应用程序中使用这些秘密。

使用您的秘密

秘密一旦创建,就需要与应用程序进行关联。这意味着 Kubernetes 需要以某种方式将秘密的值传递给正在运行的容器。Kubernetes 提供了两种将秘密链接到应用程序的方式:

  • 将秘密用作环境变量

  • 将秘密挂载为文件

将秘密挂载为文件是在应用程序中使用秘密的最佳方法。在本节中,我们将解释两种方法,并展示为什么最好使用第二种方法。

作为环境变量的秘密

在 Pod 定义中引用秘密在containersenv部分下。我们将使用之前在 Pod 中定义的秘密,并学习如何在应用程序中使用它们:

  1. 我们可以配置一个具有环境变量秘密的 Pod,就像在pod-with-env-secrets.yaml中提供的定义:
1   apiVersion: v1
2   kind: Pod
3   metadata:
4     name: secret-using-env
5   spec:
6     containers:
7     - name: nginx
8       image: nginx
9       env:
10        - name: SECRET_URL
11          valueFrom:
12            secretKeyRef:
13              name: myapi-url-token
14              key: secreturl.txt
15        - name: SECRET_TOKEN
16          valueFrom:
17            secretKeyRef:
18              name: myapi-url-token
19              key: secrettoken.txt
20    restartPolicy: Never

让我们检查一下这个文件:

第 9 行:在这里,我们设置环境变量。

第 11-14 行:在这里,我们引用myapi-url-token秘密中的secreturl.txt文件。

第 16-19 行:在这里,我们引用myapi-url-token秘密中的secrettoken.txt文件。

  1. 现在让我们创建 Pod 并看看它是否真的起作用:
kubectl create -f pod-with-env-secrets.yaml
  1. 检查环境变量是否设置正确:
kubectl exec -it secret-using-env sh
echo $SECRET_URL
echo $SECRET_TOKEN

这应该显示与图 10.33类似的结果:

通过使用 kubectl exec 在容器中打开 shell 并运行 echo 来验证环境变量是否设置正确,以获取秘密值。

图 10.33:我们可以在 Pod 内部获取秘密

任何应用程序都可以通过引用适当的env变量来使用秘密值。请注意,应用程序和 Pod 定义都没有硬编码的秘密。

将秘密作为文件

让我们看看如何将相同的秘密挂载为文件。我们将使用以下 Pod

定义以演示如何完成此操作。它在pod-with-env-secrets.yaml文件中提供:

1   apiVersion: v1
2   kind: Pod
3   metadata:
4     name: secret-using-volume
5   spec:
6     containers:
7     - name: nginx
8       image: nginx
9       volumeMounts:
10      - name: secretvolume
11        mountPath: "/etc/secrets"
12        readOnly: true
13    volumes:
14    - name: secretvolume
15      secret:
16        secretName: myapi-url-token

让我们仔细看看这个文件:

  • 第 9-12 行:在这里,我们提供挂载详细信息。我们将/etc/secrets目录挂载为只读。

  • 第 13-16 行:在这里,我们引用秘密。请注意,秘密中的两个值都将挂载到容器中。

请注意,这比env定义更简洁,因为您不必为每个秘密定义名称。但是,应用程序需要有特殊的代码来读取文件的内容,以便正确加载它。

让我们看看秘密是否传递了:

  1. 使用以下命令创建 Pod:
kubectl create -f pod-with-vol-secret.yaml
  1. 回显挂载卷中文件的内容:
kubectl exec -it secret-using-volume bash
cd /etc/secrets/ 
cat secreturl.txt
cat /etc/secrets/secrettoken.txt 

如您在图 10.34中所见,我们的 Pod 中存在秘密:

输出显示我们的 Pod 中的秘密可用作文件。

图 10.34:我们的 Pod 中的秘密可用作文件

我们已经讨论了将秘密传递给运行中容器的两种方法。在下一节中,我们将解释为什么最好使用文件方法。

为什么将秘密作为文件是最佳方法

尽管将秘密作为环境变量是一种常见做法,但将秘密作为文件挂载更安全。Kubernetes 安全地处理秘密作为环境变量,但 Docker 运行时不安全地处理它们。要验证这一点,您可以运行以下命令以在 Docker 运行时看到明文的秘密:

  1. 首先使用以下命令获取秘密 Pod 正在运行的实例:
kubectl describe pod secret-using-env | grep Node

这应该向您显示实例 ID,如图 10.35所示:

使用 kubectl describe pod secret-using-env 命令执行以获取实例 ID。

图 10.35:获取实例 ID
  1. 接下来,获取正在运行的 Pod 的 Docker ID:
kubectl describe pod secret-using-env | grep 'docker://'

这应该向您显示 Docker ID:

使用 kubectl describe pod secret-using-env 命令获取 Docker ID。

图 10.36:获取 Docker ID
  1. 最后,我们将在运行我们的容器的节点中执行一个命令,以显示我们作为环境变量传递的秘密:
INSTANCE=<provide instance number>
DOCKERID=<provide Docker ID>
VMSS=$(az vmss list --query '[].name' -o tsv)
RGNAME=$(az vmss list --query '[].resourceGroup' -o tsv)
az vmss run-command invoke -g $RGNAME -n $VMSS --command-id \
RunShellScript --instance-id $INSTANCE --scripts \
"docker inspect -f '{{ .Config.Env }}' $DOCKERID" \
-o yaml| grep SECRET

这将向您显示明文的两个秘密:

输出显示秘密在 Docker 运行时被解码

图 10.37:秘密在 Docker 运行时被解码

如您所见,秘密在 Docker 运行时被解码。这意味着任何有权访问机器的操作者都将能够访问这些秘密。这也意味着大多数日志系统将记录敏感秘密。

注意

RBAC 对于控制秘密也非常重要。拥有对集群的访问权限并具有正确角色的人可以访问存储的秘密。由于秘密只是 base64 编码的,任何具有对秘密的 RBAC 权限的人都可以解码它们。建议谨慎对待对秘密的访问,并非常小心地授予人们使用kubectl exec命令获取容器 shell 的访问权限。

让我们确保清理掉我们在这个示例中创建的资源:

kubectl delete pod --all
kubectl delete secret myapi-url-token \
myapiurltoken-literal myapiurltoken-yaml

现在我们已经使用默认的秘密机制在 Kubernetes 中探索了秘密,让我们继续使用一个更安全的选项,即 Key Vault。

使用存储在 Key Vault 中的秘密

在上一节中,我们探讨了存储在 Kubernetes 中的本地秘密。这意味着它们以 base64 编码存储在 Kubernetes API 服务器上(在后台,它们将存储在 etcd 数据库中,但这是微软提供的托管服务的一部分)。我们在上一节中看到,base64 编码的秘密根本不安全。对于高度安全的环境,您将希望使用更好的秘密存储。

Azure 提供了符合行业标准的秘密存储解决方案,称为 Azure 密钥保管库。这是一个托管服务,可以轻松创建、存储和检索秘密,并提供对秘密访问的良好监控。微软维护了一个开源项目,允许您将密钥保管库中的秘密挂载到您的应用程序中。这个解决方案称为密钥保管库 FlexVolume,可以在这里找到:github.com/Azure/kubernetes-keyvault-flexvol

在本节中,我们将创建一个密钥保管库,并安装密钥保管库 FlexVolume 以挂载存储在密钥保管库中的秘密到 Pod 中。

创建密钥保管库

我们将使用 Azure 门户创建密钥保管库:

  1. 要开始创建过程,请在 Azure 搜索栏中查找密钥保管库在 Azure 搜索栏中搜索密钥保管库。
图 10.38:在 Azure 搜索栏中查找密钥保管库
  1. 点击“添加”按钮开始创建过程:导航到左上角并点击“添加”按钮开始创建密钥保管库。
图 10.39:单击“添加”按钮开始创建密钥保管库
  1. 提供创建密钥保管库的详细信息。密钥保管库的名称必须是全局唯一的,因此考虑在名称中添加您的缩写。建议在与您的集群相同的区域创建密钥保管库:输入订阅、密钥保管库名称和区域等详细信息以创建密钥保管库。
图 10.40:提供创建密钥保管库的详细信息
  1. 提供详细信息后,点击“审核+创建”按钮以审核并创建您的密钥保管库。点击“创建”按钮完成创建过程。

  2. 创建您的密钥保管库需要几秒钟的时间。一旦保管库创建完成,打开它,转到秘密,然后点击生成/导入按钮创建一个新的秘密:导航到左侧导航中的秘密选项卡,并点击生成/导入按钮创建新秘密。

图 10.41:创建新秘密
  1. 在秘密创建向导中,提供有关您的秘密的详细信息。为了使演示更容易跟进,建议使用名称k8s-secret-demo。点击屏幕底部的创建按钮来创建秘密:输入创建新秘密的详细信息。
图 10.42:提供新秘密的详细信息

现在我们在密钥保管库中有一个秘密,我们可以继续配置 Key Vault FlexVolume 以在 Kubernetes 中访问此秘密。

设置 Key Vault FlexVolume

在本节中,我们将在我们的集群中设置 Key Vault FlexVolume。这将允许我们从 Key Vault 中检索秘密:

  1. 使用以下命令创建 Key Vault FlexVolume。kv-flexvol-installer.yaml文件已在本章的源代码中提供:
kubectl create -f kv-flexvol-installer.yaml

注意

我们已经提供了kv-flexvol-installer.yaml文件,以确保与本书中的示例一致。对于生产用例,我们建议安装最新版本,可在github.com/Azure/kubernetes-keyvault-flexvol上找到。

  1. FlexVolume 需要凭据才能连接到 Key Vault。在这一步中,我们将创建一个新的服务主体:
APPID=$(az ad app create \
    --display-name "flex" \
    --identifier-uris "https://flex" \
    --query appId -o tsv)
az ad sp create --id $APPID
APPPASSWD=$(az ad sp credential reset \
    --name $APPID \
    --credential-description "KeyVault" \
    --query password -o tsv)
  1. 现在我们将在 Kubernetes 中创建两个秘密来存储服务主体连接:
kubectl create secret generic kvcreds \
--from-literal=clientid=$APPID \
--from-literal=clientsecret=$APPPASSWD --type=azure/kv
  1. 现在我们将为此服务主体授予对密钥保管库中秘密的访问权限:
KVNAME=handsonaks-kv
az keyvault set-policy -n $KVNAME --key-permissions \
  get --spn $APPID
az keyvault set-policy -n $KVNAME --secret-permissions \
  get --spn $APPID
az keyvault set-policy -n $KVNAME --certificate-permissions \
  get --spn $APPID

您可以在门户中验证这些权限是否已成功设置。在您的密钥保管库中,在访问策略部分,您应该看到 flex 应用程序对密钥、秘密和证书具有获取权限:

在访问策略部分,我们可以看到 flex 应用程序对密钥、秘密和证书具有获取权限。

图 10.43:flex 应用程序对密钥、秘密和证书具有获取权限

注意

Key Vault FlexVolume 支持多种身份验证选项。我们现在正在使用预创建的服务主体。FlexVolume 还支持使用托管标识,可以使用 Pod 标识或使用托管集群的虚拟机规模集VMSS)的标识。本书不会探讨这些其他身份验证选项,但鼓励您在 https://github.com/Azure/kubernetes-keyvault-flexvol 上阅读更多。

这就结束了 Key Vault FlexVolume 的设置。在下一节中,我们将使用 FlexVolume 来访问秘密并将其挂载在 Pod 中。

使用 Key Vault FlexVolume 在 Pod 中挂载秘密

在这一部分,我们将把一个来自 Key Vault 的秘密挂载到一个新的 Pod 中。

  1. 我们提供了一个名为pod_secret_flex.yaml的文件,它将帮助创建一个挂载 Key Vault 秘密的 Pod。您需要对这个文件进行两处更改:
1   apiVersion: v1
2   kind: Pod
3   metadata:
4     name: nginx-secret-flex
5   spec:
6     containers:
7     - name: nginx
8       image: nginx
9       volumeMounts:
10      - name: test
11        mountPath: /etc/secret/
12        readOnly: true
13    volumes:
14    - name: test
15      flexVolume:
16        driver: "azure/kv"
17        secretRef:
18          name: kvcreds
19        options:
20          keyvaultname: <keyvault name>
21          keyvaultobjectnames: k8s-secret-demo
22          keyvaultobjecttypes: secret
23          tenantid: "<tenant ID>"

让我们调查一下这个文件:

第 9-12 行:类似于将秘密作为文件挂载的示例,我们还提供了一个volumeMount来将我们的秘密作为文件挂载。

第 13-23 行:这是指向 FlexVolume 的卷配置。

第 17-18 行:在这里,我们提到了在上一个示例中创建的服务主体凭据。

第 20-23 行:您需要更改这些值以代表您的环境。

  1. 我们可以使用以下命令创建此 Pod:
kubectl create -f pod_secret_flex.yaml
  1. 一旦 Pod 被创建并运行,我们可以在 Pod 中执行并验证秘密是否存在:
kubectl exec -it nginx-secret-flex bash
cd /etc/secret
cat k8s-secret-demo

这应该输出我们在 Key Vault 中创建的秘密,如图 10.43 所示:

显示我们在 Key Vault 中配置的秘密被挂载在 Pod 中作为文件的输出。

图 10.44:我们在 Key Vault 中配置的秘密被挂载在 Pod 中作为一个文件

我们已成功使用 Key Vault 存储秘密。秘密不再以 base64 编码的方式存储在我们的集群中,而是安全地存储在集群外的 Key Vault 中。我们仍然可以使用 Key Vault FlexVolume 在集群中访问秘密。

让我们确保清理我们的部署:

kubectl delete -f pod_secret_flex.yaml
kubectl delete -f kv-flexvol-installer.yaml
kubectl delete secret kvcreds

在这一部分,我们已经看到了创建和挂载秘密的多种方式。我们已经探讨了使用文件、YAML 文件和直接从命令行创建秘密的方法。我们还探讨了秘密可以如何被使用,可以作为环境变量或作为挂载文件。然后,我们研究了一种更安全的使用秘密的方式,即通过 Azure Key Vault。

在下一节中,我们将探讨使用 Istio 服务网格在我们的集群中配置额外的网络安全。

Istio 服务网格为您服务

我们已经找到了一些保护我们的 Pod 的方法,但我们的网络连接仍然是开放的。集群中的任何 Pod 都可以与同一集群中的任何其他 Pod 通信。作为一个站点可靠性工程师,你会希望强制执行入口和出口规则。此外,你还希望引入流量监控,并希望有更好的流量控制。作为开发人员,你不想被所有这些要求所困扰,因为你不知道你的应用将被部署在哪里,或者什么是允许的。最好的解决方案是一个工具,让我们可以按原样运行应用程序,同时指定网络策略、高级监控和流量控制。

进入服务网格。这被定义为控制服务之间通信的层。服务网格是微服务之间的网络。服务网格被实现为控制和监视不同微服务之间流量的软件。通常,服务网格利用边车来透明地实现功能。如果你记得,Kubernetes 中的 Pod 可以由一个或多个容器组成。边车是添加到现有 Pod 中以实现附加功能的容器;在服务网格的情况下,这个功能就是服务网格的功能。

注意

服务网格控制的远不止网络安全。如果你的集群只需要网络安全,请考虑采用网络策略。

与微服务一样,服务网格的实现并不是免费的午餐。如果你没有数百个微服务在运行,你可能不需要服务网格。如果你决定你真的需要一个,你首先需要选择一个。有四个流行的选项,每个都有自己的优势:

你应该根据自己的需求选择一个服务网格,并且放心任何一个解决方案都会适合你。在本节中,你将介绍 Istio 服务网格。我们选择了 Istio 服务网格,因为它很受欢迎。我们使用 GitHub 上的星星和提交作为受欢迎程度的衡量标准。

描述 Istio 服务网格

Istio 是由 IBM、Google 和 Lyft 创建的服务网格。该项目于 2017 年 5 月宣布,并于 2018 年 7 月达到稳定的 v1 版本。Istio 是服务网格的控制平面部分。Istio 默认使用Envoy sidecar。在本节中,我们将尝试解释什么是服务网格,以及 Istio 服务网格的核心功能是什么。

注意

在本书中,我们只是简要地涉及了服务网格和 Istio。Istio 不仅是一个非常强大的工具,可以保护,还可以管理云原生应用程序的流量。在本书中,我们没有涵盖很多细节和功能。

在功能方面,服务网格(特别是 Istio)具有许多核心功能。其中之一是流量管理。在这种情况下,流量管理一词意味着流量路由的控制。通过实施服务网格,您可以控制流量以实施 A/B 测试或金丝雀发布。没有服务网格,您需要在应用程序的核心代码中实施该逻辑,而有了服务网格,该逻辑是在应用程序之外实施的。

流量管理还带来了 Istio 提供的额外安全性。Istio 可以管理身份验证、授权和服务之间通信的加密。这确保只有经过授权的服务之间进行通信。在加密方面,Istio 可以实施双向 TLSmTLS)来加密服务之间的通信。

Istio 还具有实施策略的能力。策略可用于限制某些流量的速率(例如,每分钟只允许 x 个事务),处理对服务的访问的白名单和黑名单,或实施标头重写和重定向。

最后,Istio 为应用程序中不同服务之间的流量提供了巨大的可见性。Istio 可以执行流量跟踪、监控和日志记录服务之间的流量。您可以使用这些信息创建仪表板来展示应用程序性能,并使用这些信息更有效地调试应用程序故障。

在接下来的部分,我们将安装 Istio 并配置 Pod 之间流量的 mTLS 加密。这只是平台的许多功能之一。我们鼓励您走出书本,了解更多关于 Istio 的信息。

安装 Istio

安装 Istio 很容易;要这样做,请按照以下步骤操作:

  1. 转到您的主目录并下载istio软件包,如下所示:
cd ~
curl -L https://istio.io/downloadIstio | sh -
  1. istio二进制文件添加到您的路径。首先,获取您正在运行的 Istio 版本号:
ls | grep istio 

这应该显示类似于图 10.45的输出:

运行 ls | grepistio 命令显示正在运行的 Istio 版本号的输出。

图 10.45:获取您的 Istio 版本号

记下 Istio 版本,并将其用作以下方式将二进制文件添加到您的路径:

export PATH="$PATH:~/istio-<release-number>/bin"
  1. 使用以下命令检查您的集群是否可以用于运行 Istio:
istioctl verify-install
  1. 使用演示配置文件安装istio
istioctl manifest apply --set profile=demo

注意

演示配置文件非常适合用于演示 Istio,但不建议用于生产安装。

  1. 确保一切都已经启动运行,如下所示:
kubectl get svc -n istio-system

您应该在istio-system命名空间中看到许多服务:

显示 istio-system 命名空间中所有服务的输出。

图 10.46:所有 Istio 服务都已经启动运行

您现在已经安装并运行了 Istio。

自动注入 Envoy 作为边车

如本节介绍中所述,服务网格使用边车来实现功能。 Istio 具有使用命名空间中的标签自动安装其使用的Envoy边车的能力。我们可以通过以下步骤使其以这种方式运行:

  1. 让我们使用适当的标签(即istio-injection=enabled)为默认命名空间打标签:
kubectl label namespace default istio-injection=enabled
  1. 让我们启动一个应用程序,看看边车是否确实自动部署(bookinfo.yaml文件在本章的源代码中提供):
kubectl create -f bookinfo.yaml

获取在默认命名空间上运行的 Pods。Pods 可能需要几秒钟才能显示出来,并且所有 Pods 变为Running可能需要几分钟:

kubectl get pods
  1. 在任何一个 Pod 上运行describe命令:
kubectl describe pods/productpage-v1-<pod-ID>

您可以看到边车确实已经应用:

显示 Istio 自动注入边车代理列出产品页面和 istio-proxy 的详细信息的输出。

图 10.47:Istio 自动注入边车代理

请注意,即使没有对基础应用程序进行任何修改,我们也能够部署并附加 Istio 服务网格到容器。

强制使用双向 TLS

为了加密所有的服务对服务流量,我们将启用 mTLS。默认情况下,不强制使用双向 TLS。在本节中,我们将逐步强制执行 mTLS 身份验证。

注意

如果您想了解 Istio 中端到端安全框架的更多信息,请阅读 https://istio.io/docs/concepts/security/#authentication-policies。有关双向 TLS 的更多细节,请阅读 https://istio.io/docs/concepts/security/#mutual-tls-authentication。

部署示例服务

在这个例子中,您将部署两个服务,httpbinsleep,在不同的命名空间下。其中两个命名空间,foobar,将成为服务网格的一部分。这意味着它们将拥有istio sidecar 代理。您将在这里学习一种不同的注入 sidecar 的方式。第三个命名空间,legacy,将在没有 sidecar 代理的情况下运行相同的服务:

我们将要构建的演示应用程序的图形表示。图像显示网格中的两个命名空间和网格外的一个命名空间。每个命名空间都有两个 pod,sleep 和 httpbin。

图 10.48:三个命名空间中的两个在服务网格中

我们将使用以下命令查看命名空间的服务:

  1. 首先,我们创建命名空间(foobarlegacy),并在这些命名空间中创建httpbinsleep服务:
kubectl create ns foo
kubectl apply -f <(istioctl kube-inject \
-f httpbin.yaml) -n foo 
kubectl apply -f <(istioctl kube-inject \
-f sleep.yaml) -n foo
kubectl create ns bar
kubectl apply -f <(istioctl kube-inject \
-f httpbin.yaml) -n bar
kubectl apply -f <(istioctl kube-inject \
-f sleep.yaml) -n bar
kubectl create ns legacy
kubectl apply -f httpbin.yaml -n legacy 
kubectl apply -f sleep.yaml -n legacy

正如您所看到的,我们现在正在使用istioctl工具来注入 sidecar。它会读取我们的 YAML 文件,并在部署中注入 sidecar。现在我们在foobar命名空间中有一个带有 sidecar 的服务。但是在legacy命名空间中没有注入。

  1. 让我们检查一下是否一切都部署成功了。为了检查这一点,我们提供了一个脚本,它会从每个命名空间到所有其他命名空间建立连接。该脚本将输出每个连接的 HTTP 状态码。显示脚本如何使用休眠 pod 测试所有连接,并在每个命名空间中与 httpbin 建立连接的图表。
图 10.49:脚本将测试所有连接
  1. 使用以下命令运行脚本:
bash test_mtls.sh

上述命令会迭代所有可达的组合。您应该看到类似以下输出。HTTP 状态码为200表示成功:

bash test_mtls.sh 命令的输出显示 HTTP 状态码为 200,表示所有 pod 都成功,并且证明每个 Pod 都可以与所有其他 Pod 通信。

图 10.50:没有任何策略,我们可以成功地从每个命名空间连接到其他命名空间

这向我们表明,在当前配置中,所有 Pod 都可以与所有其他 Pod 通信。

  1. 确保除默认策略外没有现有策略,如下所示:
kubectl get policies.authentication.istio.io \
--all-namespaces
kubectl get meshpolicies.authentication.istio.io

这应该向您展示:

验证只有两个策略存在。

图 10.51:只应存在两个策略
  1. 另外,确保没有适用的目标规则:
kubectl get destinationrules.networking.istio.io \
--all-namespaces -o yaml | grep "host:"

在结果中,不应该有带有foobarlegacy或通配符(表示为*)的主机。

确保没有适用的目标规则,并且没有带有 foo、bar、legacy 或*通配符的主机。

图 10.52:不应该有带有 foo、bar、legacy 或*通配符的主机

我们已成功部署了示例服务,并且能够确认在默认情况下,所有服务都能够彼此通信。

全局启用 mTLS

使用 mTLS 策略,您可以声明所有服务在与其他服务通信时必须使用 mTLS。如果不使用 mTLS,即使恶意用户可以访问集群,即使他们无法访问命名空间,也可以与任何 Pod 通信。如果拥有足够的权限,他们还可以在服务之间充当中间人。在服务之间实施 mTLS 减少了中间人攻击的可能性:

  1. 要全局启用双向 TLS,我们将创建以下MeshPolicy(在mtls_policy.yaml中提供):
1   apiVersion: authentication.istio.io/v1alpha1
2   kind: MeshPolicy
3   metadata:
4     name: default
5   spec:
6     peers:
7     - mtls: {}

由于此MeshPolicy没有选择器,它将适用于所有工作负载。网格中的所有工作负载只接受使用 TLS 加密的请求。这意味着MeshPolicy处理连接的传入部分:

图形表示显示 MeshPolicy 如何应用于传入连接。图片显示 foo 和 bar 命名空间中的 httpbin 是 MeshPolicy 的一部分。

图 10.53:MeshPolicy 适用于传入连接

您可以使用以下命令创建MeshPolicy

kubectl apply -f mtls_policy.yaml

注意

我们正在粗暴和激进地应用 mTLS。通常,在生产系统中,您会慢慢引入 mTLS。Istio 有一个特殊的 mTLS 强制模式称为permissive,可以帮助实现这一点。在permissive模式下使用 mTLS,Istio 将尝试在可能的情况下实现 mTLS,并在不可能的情况下记录警告。但是,流量将继续流动。

  1. 现在再次运行脚本以测试网络连接:
bash test_mtls.sh
  1. 带有边车的系统在运行此命令时将失败,并将收到503状态代码,因为客户端仍在使用纯文本。可能需要几秒钟才能使MeshPolicy生效。图 10.54显示输出:bash test_mtls.sh 命令的输出,显示具有边车的 httpbin.foo 和 httpbin.barPods 的流量失败,状态代码为 503。
图 10.54:带有边车的 Pod 的流量失败,状态代码为 503

如果在先前的输出中更频繁地看到200状态代码,请考虑等待几秒钟,然后重新运行测试。

  1. 现在,我们将通过将目标规则设置为使用类似于整个网格的身份验证策略的*通配符来允许某些流量。这是配置客户端端的必需操作:
1   apiVersion: networking.istio.io/v1alpha3
2   kind: DestinationRule
3   metadata:
4     name: default
5     namespace: istio-system
6   spec:
7     host: "*.local"
8     trafficPolicy:
9       tls:
10        mode: ISTIO_MUTUAL

让我们看看这个文件:

  • 第 2 行:在这里,我们正在创建一个DestinationRule,它定义了在路由发生后适用于服务流量的策略。

  • 第 7 行:任何.local中的主机的流量(在我们的情况下是集群中的所有流量)应使用此策略。

  • 第 8-10 行:在这里,我们定义所有流量都需要 mTLS。

DestinationRule适用于连接的传出部分,如图 10.55所示:

图形表示显示 DestinationRule 适用于连接的传出部分。图像显示 foo 和 bar 名称空间中的 sleep 是 DestinationRule 的一部分。

图 10.55:DestinationRule 适用于传出流量

我们可以使用以下命令创建这个:

kubectl create -f destinationRule.yaml

我们可以通过再次运行相同的命令来检查其影响:

bash test_mtls.sh

这次,返回的代码将如图 10.56所示:

重新运行 bash test_mtls.sh 命令,显示 foo 和 bar 现在可以相互连接,但它们无法再连接到 legacy。

图 10.56:foo 和 bar 现在可以相互连接,但它们无法再连接到 legacy

让我们考虑一下我们在这种情况下实施了什么。我们实施了:

一个需要 Pod 中传入的 TLS 流量的集群范围策略。

一个需要对外流量进行 mTLS 的集群范围 DestinationRule。

这样做的影响是网格内的服务(也称为具有边车)现在可以使用 mTLS 相互通信,并且完全不在网格内的服务(也称为没有边车)也可以相互通信,只是没有 mTLS。由于我们当前的配置,当流的一部分在网格中时,服务之间的通信会中断。

让我们确保清理我们部署的任何资源:

istioctl manifest generate --set profile=demo | kubectl delete -f -
for NS in "foo" "bar" "legacy"
do
kubectl delete -f sleep.yaml -n $NS
kubectl delete -f httpbin.yaml -n $NS
done
kubectl delete -f bookinfo.yaml

这结束了 Istio 的演示。

总结

在本章中,我们专注于 Kubernetes 中的安全性。我们从使用 Azure AD 中的身份查看了集群 RBAC。之后,我们继续讨论在 Kubernetes 中存储秘密。我们详细介绍了创建、解码和使用秘密。最后,我们安装并注入了 Istio,实现了能够设置系统范围策略而无需开发人员干预或监督的目标。由于黑客喜欢攻击易受攻击的系统,您在本章中学到的技能将有助于使您的设置不太可能成为目标。

在接下来的最后一章中,您将学习如何在 Azure Kubernetes Service(AKS)上部署无服务器函数。

第十一章:无服务器函数

在过去几年中,无服务器和无服务器函数已经获得了巨大的发展。Azure Functions、AWS Lambda 和 GCP Cloud Run 等云服务使开发人员非常容易地将其代码作为无服务器函数运行。

“无服务器”一词指的是您无需管理服务器的任何解决方案。无服务器函数是指无服务器计算的子集,您可以按需将代码作为函数运行。这意味着函数中的代码只有在有“需求”时才会运行和执行。这种架构风格称为事件驱动架构。在事件驱动架构中,事件消费者在有事件发生时被触发。在无服务器函数的情况下,事件消费者将是这些无服务器函数。事件可以是从队列上的消息到上传到存储的新对象,甚至是 HTTP 调用的任何内容。

无服务器函数经常用于后端处理。无服务器函数的一个常见示例是创建上传到存储的图片的缩略图。由于无法预测将上传多少图片以及它们何时上传,很难规划传统基础设施以及为此过程应该准备多少服务器。如果将缩略图的创建实现为无服务器函数,该函数将在上传的每张图片上被调用。您无需规划函数的数量,因为每张新图片都将触发执行一个新函数。

这种自动扩展只是使用无服务器函数的一个好处。正如您在前面的示例中看到的,函数将自动扩展以满足增加或减少的需求。此外,每个函数可以独立于其他函数进行扩展。无服务器函数的另一个好处是对开发人员的易用性。无服务器函数允许代码部署而无需担心管理服务器和中间件。最后,在公共云无服务器函数中,您按照函数的执行付费。这意味着每次运行函数时都要付费,并且在函数不运行时不收取任何费用。

公共云无服务器函数平台的流行导致了多个开源框架的创建,使用户能够在 Kubernetes 之上创建无服务器函数。在本章中,您将学习如何使用 Azure Functions 的开源版本直接在Azure Kubernetes Services (AKS)上部署无服务器函数。您将首先运行一个简单的函数,该函数是基于 HTTP 消息触发的。之后,您将在集群上安装函数的自动缩放功能。您还将把 AKS 部署的应用程序与 Azure 存储队列集成。我们将涵盖以下主题:

  • 不同函数平台的概述

  • 部署基于 HTTP 触发的函数

  • 部署队列触发的函数

让我们从探索适用于 Kubernetes 的多个函数平台开始这一章。

多个函数平台

诸如 Azure Functions、AWS Lambda 和 Google Cloud Functions 之类的函数平台在流行度上获得了巨大的增长。无需考虑服务器即可运行代码并且具有几乎无限的扩展性非常受欢迎。使用云提供商的函数实现的缺点是您被锁定在他们的基础设施和编程模型中。此外,您只能在公共云中运行函数,而不能在自己的数据中心中运行。

已经推出了许多开源函数框架来解决这些缺点。有许多流行的框架:

  • Serverless (serverless.com/): 基于 Node.js 的无服务器应用程序框架,可以在多个云提供商上部署和管理函数,包括 Azure。通过 Kubeless 提供 Kubernetes 支持。

  • OpenFaaS (www.openfaas.com/): OpenFaaS 是一个 Kubernetes 原生的无服务器框架。它可以在托管的 Kubernetes 环境(如 AKS)或自托管集群上运行。OpenFaaS 也作为 OpenFaaSCloud 的托管云服务提供。该平台是用 Go 语言编写的。

  • Fission.io (fission.io/): Fission 是用 Go 语言编写的,是 Kubernetes 原生的。这是一个由 Platform9 公司支持的无服务器框架。

  • Apache OpenWhisk(openwhisk.apache.org/):OpenWhisk 是一个由 Apache 组织维护的开源分布式无服务器平台。它可以在 Kubernetes、Mesos 和 Docker Compose 上运行。它主要用 Scala 语言编写。

  • Knativecloud.google.com/knative/):Knative 是由 Google 开发的用 Go 语言编写的无服务器函数平台。您可以在 Google Cloud 上完全托管 Knative 函数,也可以在自己的 Kubernetes 集群上运行。

微软采取了一种有趣的策略来处理其函数平台。微软在 Azure 上作为托管服务运行 Azure Functions,并已开源了完整解决方案,并可在任何系统上运行(github.com/Azure/azure-functions-host)。这也使得 Azure Functions 编程模型可以在 Kubernetes 上运行。

微软还与红帽合作发布了一个名为Kubernetes 事件驱动自动缩放KEDA)的额外开源项目,以使在 Kubernetes 上扩展函数更加容易。KEDA是一个自定义自动缩放器,可以允许部署从 0 个 Pod 缩放到 1 个 Pod。从 0 扩展到 1 个 Pod 很重要,这样您的应用程序就可以开始处理事件。缩减到 0 个实例对于保留集群中的资源很有用。使用 Kubernetes 中的默认水平 Pod 自动缩放器(HPA)无法实现从 0 到 1 个 Pod 的扩展。

KEDA 还可以提供额外的指标给 Kubernetes HPA,以便根据集群外部的指标(例如队列中的消息数量)做出扩展决策。

注意

我们在第四章中介绍和解释了 HPA,扩展您的应用程序

在本章中,我们将在两个示例中将 Azure Functions 部署到 Kubernetes:

  • 一个 HTTP 触发的函数

  • 一个队列触发的函数

在开始之前,我们需要设置一个Azure 容器注册表ACR)和一个开发机器。ACR 将用于存储包含我们将开发的函数的自定义 Docker 映像。我们将使用开发机器构建函数并创建 Docker 映像。

设置先决条件

在本节中,我们将设置我们构建和运行函数所需的先决条件。我们需要一个容器注册表和一个开发机器。

第一章Docker 和 Kubernetes 简介中的Docker 镜像部分,我们介绍了容器镜像和容器注册表。容器镜像包含启动实际运行容器所需的所有软件。在本章中,我们将构建包含我们函数的自定义 Docker 镜像。我们需要一个地方来存储这些镜像,以便 Kubernetes 可以拉取这些镜像并以规模运行容器。我们将使用 Azure 容器注册表来实现这一点。Azure 容器注册表是由 Azure 完全管理的私有容器注册表。

到目前为止,在本书中,我们已经在 Azure Cloud Shell 上运行了所有的示例。对于本章的示例,我们需要一个单独的开发机器,因为 Azure Cloud Shell 不允许您构建 Docker 镜像。我们将在 Azure 上创建一个新的开发机器来执行这些任务。

让我们开始创建一个 ACR。

Azure 容器注册表

Kubernetes 上的 Azure 函数需要一个镜像注册表来存储其容器镜像。在本节中,我们将创建一个 ACR 并配置我们的 Kubernetes 集群以访问此注册表:

  1. 在 Azure 搜索栏中,搜索容器注册表,然后单击容器注册表在 Azure 搜索栏中输入关键词“容器注册表”以查找并选择容器注册表。
图 11.1:在搜索栏中查找容器注册表
  1. 单击顶部的添加按钮以创建一个新的注册表。提供创建注册表的详细信息。注册表名称需要全局唯一,因此考虑在注册表名称中添加您的缩写。建议在与您的集群相同的位置创建注册表。选择创建按钮以创建注册表:在创建容器注册表窗口中输入注册表名称、订阅、资源组、位置和 SKU 等详细信息。
图 11.2:提供创建注册表的详细信息
  1. 当您的注册表创建好后,打开 Cloud Shell,这样我们就可以配置我们的 AKS 集群以访问我们的容器注册表。使用以下命令为您的注册表授予 AKS 权限:
az aks update -n handsonaks -g rg-handsonaks --attach-acr <acrName>

我们现在有了一个与 AKS 集成的 ACR。在下一节中,我们将创建一个开发机器,用于构建 Azure 函数。

创建开发机器

在本节中,我们将创建一个开发机器并安装在该机器上运行 Azure 函数所需的工具:

  • Docker 运行时

  • Azure CLI

  • Azure Functions

  • Kubectl

注意

为了确保一致的体验,我们将在 Azure 上创建一个将用于开发的虚拟机(VM)。如果您希望在本地机器上运行示例,可以在本地安装所有所需的工具。

让我们开始创建这台机器:

  1. 首先,我们将生成一组用于连接到 VM 的 SSH 密钥:
ssh-keygen

系统会提示您输入位置和密码。保持默认位置并输入空密码。

注意

如果您按照第十章中的示例保护您的 AKS 集群,在那里我们创建了一个 Azure AD 集成的集群,您可以跳过第 1 步,因为您已经有了一组 SSH 密钥。

如果您希望重用已有的 SSH 密钥,也可以这样做。

  1. 现在我们将创建我们的开发机器。我们将使用以下命令创建一个 Ubuntu VM:
az vm create -g rg-handsonaks -n devMachine \
  --image UbuntuLTS --ssh-key-value ~/.ssh/id_rsa.pub \
  --admin-username handsonaks --size Standard_D1_v2
  1. 这将需要几分钟的时间才能完成。创建 VM 后,Cloud Shell 应该会显示其公共 IP,如图 11.3中所示:显示位置、MAC 地址、电源状态、私有 IP 和 Ubuntu VM 的公共 IP 等详细信息的输出。输出显示 VM 的公共 IP 地址。
图 11.3:连接到机器的公共 IP

使用以下命令连接到 VM:

ssh handsonaks@<public IP>

系统会询问您是否信任该机器的身份。输入yes以确认。

  1. 您现在已连接到 Azure 上的一台新机器。在这台机器上,我们将开始安装 Docker:
sudo apt-get update
sudo apt-get install docker.io -y
sudo systemctl enable docker
sudo systemctl start docker
  1. 要验证 Docker 是否已安装并运行,可以运行以下命令:
sudo docker run hello-world

这应该向您显示来自 Docker 的hello-world消息:

sudodocker run hello-world 命令的输出显示消息“Hello! from Docker”。

图 11.4:使用 Docker 运行 hello-world
  1. 为了使操作更加顺畅,我们将把我们的用户添加到 Docker 组,这样在 Docker 命令前将不再需要sudo
sudo usermod -aG docker handsonaks
newgrp docker

现在您应该能够在不使用sudo的情况下运行hello-world命令:

docker run hello-world
  1. 接下来,我们将在开发机器上安装 Azure CLI。您可以使用以下命令安装 CLI:
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
  1. 通过登录验证安装了 CLI:
az login

这将显示一个登录代码,您需要在microsoft.com/devicelogin输入:

az CLI 命令的输出指示用户使用 Web 浏览器输入代码。

图 11.5:登录到 az CLI

浏览到该网站,并粘贴提供给您的登录代码,以使您能够登录到 Cloud Shell。请确保在您已登录的浏览器中进行此操作,该浏览器具有访问您的 Azure 订阅的用户权限。

现在,我们可以使用 CLI 将我们的机器认证到 ACR。可以使用以下命令完成:

az acr login -n <registryname>

这将显示一个警告,指出密码将以未加密的形式存储。您可以忽略这一点,以便进行演示。

ACR 的凭据在一定时间后会过期。如果在此演示过程中遇到以下错误,您可以使用前面的命令重新登录 ACR:

错误消息指出推送 Docker 镜像失败,并且需要进行身份验证。

图 11.6:如果遇到此错误,您可以通过重新登录 ACR 来解决此问题
  1. 接下来,我们将在我们的机器上安装kubectlaz CLI 有一个安装 CLI 的快捷方式,我们将使用它:
sudo az aks install-cli

让我们验证kubectl是否可以连接到我们的集群。为此,我们首先获取凭据,然后执行一个kubectl命令:

az aks get-credentials -n handsonaks -g rg-handsonaks
kubectl get nodes
  1. 现在,我们可以在这台机器上安装 Azure Functions 工具。要做到这一点,请运行以下命令:
wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install azure-functions-core-tools -y

注意

如果您使用的 Ubuntu 版本比 18.04 更新,请确保通过更改第一步中的 URL 来下载正确的dpkg软件包以反映您的 Ubuntu 版本。

现在,我们已经具备了在 Kubernetes 上使用函数的先决条件。我们创建了一个 ACR 来存储我们的自定义 Docker 镜像,并且有一台开发机器,我们将用它来创建和构建 Azure 函数。在下一节中,我们将构建一个首个 HTTP 触发的函数。

创建一个 HTTP 触发的 Azure 函数

在这个第一个示例中,我们将创建一个 HTTP 触发的 Azure 函数。这意味着您可以浏览到托管实际函数的页面:

  1. 首先,我们将创建一个新目录并导航到该目录:
mkdir http
cd http
  1. 现在,我们将使用以下命令初始化一个函数。--docker参数指定我们将构建我们的函数作为 Docker 容器。这将导致为我们创建一个 Dockerfile。我们将在以下截图中选择 Python 语言,选项3
func init --docker

这将创建我们的函数所需的文件:

运行 funcinit --docker 命令后,提供了四个选项:dotnet、node、python 和 powershell。我们选择选项 3。

图 11.7:创建一个 Python 函数
  1. 接下来,我们将创建实际的函数。输入以下代码,并选择第五个选项,HTTP 触发器,并将函数命名为python-http
func new

这应该会产生一个类似图 11.8的输出:

func new 命令的输出返回九个选项。我们选择选项 5:HTTP 触发器。

图 11.8:创建一个 HTTP 触发函数
  1. 函数的代码存储在名为python-http的目录中。我们不打算对函数进行代码更改。如果您想查看函数的源代码,可以运行以下命令:
cat python-http/__init__.py
  1. 我们需要对函数的配置文件进行一次更改。默认情况下,函数需要经过身份验证的请求。我们将在演示中将其更改为匿名。我们将通过执行以下命令在vi中进行更改:
vi python-http/function.json

我们将在第 5 行将authLevel替换为anonymous。要进行此更改,请执行以下步骤:

I进入插入模式。

删除function并替换为anonymous

在 python-http/function.json 文件中,我们将 authlevel 更改为 anonymous。

图 11.9:将函数更改为匿名
  • Esc,输入:wq!,然后按Enter保存并退出vi

注意

我们将更改函数的身份验证要求为anonymous。这将使我们的演示更容易执行。如果您计划将函数发布到生产环境,您需要仔细考虑此设置,因为这控制着谁可以访问您的函数。

  1. 现在我们准备将函数部署到 AKS。我们可以使用以下命令部署函数:
func kubernetes deploy --name python-http \
--registry <registry name>.azurecr.io

这将导致函数的运行时执行一些步骤。首先,它将构建一个容器映像,然后将该映像推送到我们的注册表,最后将函数部署到 Kubernetes:

funckubernetes deploy 命令的输出显示 docker build,docker push 以及创建一个 secret,一个 service 和一个 deployment。

图 11.10:将函数部署到 AKS
  1. 这将在 Kubernetes 之上创建一个常规 Pod。要检查 Pods,您可以运行以下命令:
kubectl get pods
  1. 一旦该 Pod 处于运行状态,您可以获取部署的 Service 的公共 IP 并连接到它:
kubectl get service

打开一个网络浏览器,浏览到http://<external-ip>/api/python-http?name=handsonaks。您应该会看到一个网页,上面显示着Hello handsonaks!,这就是我们的函数应该显示的内容。

浏览器打印消息“Hello handsonaks!”

图 11.11:我们的函数正在正常工作

我们现在已经创建了一个带有 HTTP 触发器的函数。在转到下一部分之前,让我们清理一下这个部署:

Kubectl delete deploy python-http-http
kubectl delete service python-http-http
kubectl delete secret python-http

在这一部分,我们使用 HTTP 触发器创建了一个示例函数。让我们进一步将一个新函数与存储队列集成并设置自动缩放。

创建队列触发的函数

在上一部分,我们创建了一个示例 HTTP 函数。在实际应用中,队列通常用于在应用程序的不同组件之间传递消息。可以根据队列中的消息触发函数,然后对这些消息进行额外处理。

在这一部分,我们将创建一个与存储队列集成以消耗事件的函数。我们还将配置 KEDA 以允许在低流量情况下从 0 个 Pod 进行扩展/缩减。

我们仍然首先在 Azure 中创建一个队列。

创建队列

在这一部分,我们将创建一个新的存储帐户和一个新的队列。我们将在下一部分将函数连接到该队列。

  1. 首先,我们将创建一个存储帐户。在 Azure 搜索栏中搜索“存储”并选择存储帐户通过在 Azure 搜索栏中输入“存储”来搜索存储帐户。
图 11.12:在 Azure 搜索栏中寻找存储
  1. 点击顶部的添加按钮创建新帐户。提供详细信息以创建存储帐户。存储帐户名称必须是全局唯一的,因此考虑添加您的缩写。建议在与您的 AKS 集群相同的区域创建存储帐户。最后,为了节约成本,建议将复制设置降级为本地冗余存储(LRS)在基本选项卡中输入存储帐户详细信息,例如订阅、资源组、存储帐户名称、位置、性能、帐户类型、复制和访问层。
图 11.13:提供创建存储帐户的详细信息

如果您准备好了,请点击底部的审阅和创建按钮。在审阅屏幕上,选择创建开始创建过程。

  1. 创建存储账户大约需要一分钟。创建完成后,点击转到资源按钮打开账户。在存储账户刀片中,转到访问密钥,并复制主连接字符串。暂时记下这个字符串:在左侧面板中导航到访问密钥选项卡并复制主连接字符串。
图 11.14:复制主连接字符串

注意

对于生产用例,不建议使用访问密钥连接到 Azure 存储。拥有该访问密钥的任何用户都可以完全访问存储账户,并且可以读取和删除其中的所有文件。建议要么生成一个共享访问签名SAS)令牌来连接到存储,要么使用 Azure AD 集成安全性。要了解有关使用 SAS 令牌对存储进行身份验证的更多信息,请参阅docs.microsoft.com/rest/api/storageservices/delegate-access-with-shared-access-signature。要了解有关 Azure AD 对 Azure 存储进行身份验证的更多信息,请参阅docs.microsoft.com/rest/api/storageservices/authorize-with-azure-active-directory

  1. 最后一步是在存储账户中创建我们的队列。在左侧导航中查找queue,点击+Queue按钮添加一个队列,并为其提供一个名称。要跟随这个演示,请将队列命名为function在存储账户中创建一个新队列。
图 11.15:创建一个新队列

我们现在在 Azure 中创建了一个存储账户并获得了它的连接字符串。我们在这个存储账户中创建了一个队列。在下一节中,我们将创建一个将从队列中消耗消息的函数。

创建一个队列触发的函数

在上一节中,我们在 Azure 上创建了一个队列。在本节中,我们将创建一个新的函数,用于监视这个队列。我们需要使用这个队列的连接字符串来配置这个函数:

  1. 我们将从创建一个新目录并导航到它开始:
mkdir ~/js-queue
cd ~/js-queue
  1. 现在我们可以创建函数。我们将从初始化开始:
func init --docker
<select node, option 2>
<select javascript, option 1>

这应该导致图 11.16中显示的输出:

funcinit --docker 命令的输出要求设置两个配置设置。我们用 2(node)回答第一个,用 1(JavaScript)回答第二个。

图 11.16:初始化新函数

初始化后,我们可以创建实际的函数:

func new
<select Azure queue storage trigger, option 10>
<provide a name, suggested name: js-queue>

这应该会产生图 11.17中显示的输出:

使用 func new 命令创建实际函数,然后选择选项 10,Azure 队列存储触发器作为模板。

图 11.17:创建新函数
  1. 现在我们需要进行一些配置更改。我们需要为函数提供连接到 Azure 存储的连接字符串,并提供队列名称。首先,打开local.settings.json文件以配置存储的连接字符串:
vi local.settings.json

要进行更改,请按照以下说明操作:

  • I进入插入模式。

  • AzureWebJobsStorage(第 6 行)的行上,用之前复制的连接字符串替换该值。在该行的末尾添加逗号。

  • 添加一行,然后在该行上添加以下文本:

"QueueConnString": "<your connection string>"

通过编辑 local.settings.json 文件更改 AzureWebJobsStorage 参数的值并添加 QueueConnString。

图 11.18:编辑 local.settings.json 文件
  • 保存并关闭文件,按下Esc键,输入:wq!,然后按Enter键。
  1. 我们需要编辑的下一个文件是函数配置本身。在这里,我们将引用之前的连接字符串,并提供队列名称。为此,请使用以下命令:
vi js-queue/function.json

要进行更改,请按照以下说明操作:

  • I进入插入模式。

  • 在第 7 行,将队列名称更改为我们创建的队列的名称(function)。

  • 在第 8 行,将QueueConnString添加到连接字段中:通过编辑 js-queue/function.json 文件输入 queueName 并将连接指向 QueueConnString。

图 11.19:编辑 js-queue/function.json 文件
  • 保存并关闭文件,按下Esc键,输入:wq!,然后按Enter键。
  1. 我们现在准备将我们的函数发布到 Kubernetes。我们将通过在 Kubernetes 集群上设置 KEDA 来开始发布:
kubectl create ns keda
func kubernetes install --keda --namespace keda

这将在我们的集群上设置 KEDA。安装过程不会花费很长时间。要验证安装是否成功,请确保 KEDA Pod 正在运行:

kubectl get pod -n keda
  1. 现在我们可以将我们的函数部署到 Kubernetes。我们将配置 KEDA 每 5 秒查看一次队列消息数量(polling-interval=5),最多有 15 个副本(max-replicas=15),并在删除 Pod 之前等待 15 秒(cooldown-period=15)。要部署和配置 KEDA,请使用以下命令:
func kubernetes deploy --name js-queue \
--registry <registry name>.azurecr.io \
--polling-interval=5 --max-replicas=15 --cooldown-period=15

要验证部署,可以运行以下命令:

kubectl get all

这将显示部署的所有资源。正如您在图 11.20中所看到的,此部署创建了一个部署、ReplicaSet 和 HPA。在 HPA 中,您应该看到当前没有副本在运行:

kubectl get all 命令的输出显示创建了三个对象,并突出显示水平 Pod 自动缩放器当前没有运行副本。

图 11.20:部署创建了三个对象,现在我们没有运行副本
  1. 我们现在将在队列中创建一条消息,以唤醒部署并创建一个 Pod。要查看扩展事件,请运行以下命令:
kubectl get hpa -w
  1. 在队列中创建一条消息,我们将打开一个新的云 shell 会话。要打开新会话,请在云 shell 中选择打开新会话按钮:在 Bash 窗口中选择打开新会话按钮。
图 11.21:打开一个新的云 shell 实例
  1. 在这个新的 shell 中,运行以下命令在队列中创建一条消息。
az storage message put --queue-name function --connection-string <your connection string> --content "test"

创建完这条消息后,切换回到之前的 shell。可能需要几秒钟,但很快,您的 HPA 应该会扩展到 1 个副本。之后,它还应该缩减到 0 个副本:

执行 kubectl get hpa -w 命令并验证 KEDA 在我们在队列中创建消息时将我们的 Pod 从 0 扩展到 1,并在没有消息时缩减到 0。

图 11.22:在队列中有一条消息时,KEDA 从 0 扩展到 1,然后再次缩减到 0 个副本

我们现在已经创建了一个根据队列中消息数量触发的函数。我们能够验证 KEDA 在我们在队列中创建消息时将我们的 Pod 从 0 扩展到 1,并在没有消息时缩减到 0。在下一节中,我们将执行一个规模测试,并创建多条消息在队列中,看看函数的反应。

规模测试函数

在前面的部分中,我们看到了当队列中有单个消息时函数的反应。在这个例子中,我们将向队列中发送 1,000 条消息,看看 KEDA 如何首先扩展我们的函数,然后缩小,最终缩减到零:

  1. 在当前的云 shell 中,使用以下命令观察 HPA:
kubectl get hpa -w
  1. 要开始推送消息,我们将打开一个新的云 shell 会话。要打开新会话,请在云 shell 中选择“打开新会话”按钮:
图 11.23:打开一个新的云 shell 实例
  1. 要将 1,000 条消息发送到队列中,我们提供了一个名为sendMessages.py的 Python 脚本,在代码包中。Cloud Shell 已经安装了 Python 和 pip(Python 包管理器)。要运行此脚本,您首先需要安装两个依赖项:
pip3 install azure
pip3 install azure-storage-blob==12.0.0

安装完毕后,打开sendMessages.py文件:

code sendMessages.py

编辑第 4 行的存储连接字符串为您的连接字符串:

在 sendMessages.py 文件中,将第 4 行的存储连接字符串编辑为我们的连接字符串。

图 11.24:在第 4 行粘贴您的存储账户的连接字符串
  1. 一旦您粘贴了连接字符串,您就可以执行 Python 脚本,向您的队列发送 1,000 条消息:
python3 sendMessages.py

在消息发送的同时,切换回到之前的云 shell 实例,并观察 KEDA 从 0 扩展到 1,然后观察 HPA 扩展到最大的 15 个副本。HPA 使用 KEDA 提供的指标来做出扩展决策。默认情况下,Kubernetes 不知道 KEDA 提供给 HPA 的 Azure 存储队列中的消息数量。

一旦队列为空,KEDA 将缩减到 0 个副本:

执行 kubectl get hpa -w 命令显示 HPA 将从 0 扩展副本到 1,然后到 4,8 和 15,最后回到 0。

图 11.25:KEDA 将从 0 扩展到 1,HPA 将扩展到 15 个 Pod

这结束了我们在 Kubernetes 上运行无服务器函数的示例。让我们确保清理我们的部署。从我们创建的开发机器中运行以下命令(最后一步将删除此虚拟机。如果您想保留虚拟机,请不要运行最后一步):

kubectl delete secret js-queue
kubectl delete scaled object js-queue
kubectl delete deployment js-queue
func kubernetes remove --namespace keda
az vm delete -g rg-handsonaks -n devMachine

注意

删除 KEDA 将显示一些错误。这是因为我们只在我们的集群上安装了 KEDA 的一个子集,而删除过程尝试删除所有组件。

在本节中,我们运行了一个在 Kubernetes 上由存储队列上的消息触发的函数。我们使用了一个名为 KEDA 的组件来实现我们集群中的扩展。我们看到了 KEDA 如何从 0 扩展到 1,然后再缩减到 0。我们还看到了 HPA 如何使用 KEDA 提供的指标来扩展部署。

总结

在本章中,我们在我们的 Kubernetes 集群上部署了无服务器函数。为了实现这一点,我们首先创建了一个开发机器和一个 Azure 容器注册表。

我们通过部署使用 HTTP 触发器的函数来开始我们的函数部署。使用 Azure 函数核心工具创建该函数并将其部署到 Kubernetes。

之后,我们在我们的 Kubernetes 集群上安装了一个名为 KEDA 的附加组件。KEDA 允许 Kubernetes 中的无服务器扩展:它允许部署到 0 个 Pod,并提供额外的指标给水平 Pod 自动缩放器HPA)。我们使用了一个在 Azure 存储队列中的消息触发的函数。

本章也总结了本书。在整本书中,我们通过多个实际操作的例子介绍了 AKS。书的第一部分侧重于启动应用程序。我们创建了一个 AKS 集群,部署了多个应用程序,并学习了如何扩展这些应用程序。

书的第二部分侧重于运行 AKS 的操作方面。我们讨论了常见的故障以及如何解决它们,将应用程序集成到 Azure AD,并研究了集群的监控。

在书的最后部分,我们深入研究了 AKS 与其他 Azure 服务的高级集成。我们将我们的 AKS 集群与 Azure 数据库和 Azure 事件中心集成,保护了我们的集群,最后,在我们的 AKS 集群上开发了 Azure 函数。

通过完成这本书,您现在应该准备好在 AKS 上构建和运行您的应用程序。

posted @ 2024-05-06 18:34  绝不原创的飞龙  阅读(25)  评论(0编辑  收藏  举报