Azure-上的-Kubernetes-实用指南第三版-全-

Azure 上的 Kubernetes 实用指南第三版(全)

原文:annas-archive.org/md5/06947e7ca5047cb93d1cc16248ba5181

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章:《Azure 上的 Kubernetes 实战》第三版

使用 Azure Kubernetes 服务自动管理、扩展和部署容器化应用程序。

Nills Franssens

Shivakumar Gopalakrishnan

Gunther Lenz

《Azure 上的 Kubernetes 实战》第三版

版权所有 © 2021 Packt Publishing

保留所有权利。未经出版商事先书面许可,本书的任何部分不得以任何形式或通过任何方式复制、存储在检索系统中或传播,但在关键性文章或评论中嵌入简短引用除外。

在本书的编写过程中,已尽一切努力确保所呈现信息的准确性。然而,本书所包含的信息是无保修的,无论是明示的还是暗示的。无论是作者,还是 Packt Publishing 及其代理商和分销商,都不对因本书直接或间接造成的或声称造成的任何损害承担责任。

Packt Publishing 已尽力通过适当使用大写字母提供本书中提到的所有公司和产品的商标信息。然而,Packt Publishing 无法保证这些信息的准确性。

作者: Nills Franssens, Shivakumar Gopalakrishnan, 和 Gunther Lenz

技术审阅: Richard Hooper 和 Swaminathan Vetri

总编辑: Aditya Datar 和 Siddhant Jain

编辑: Ben Renow-Clarke

生产编辑: Deepak Chavan

编辑委员会: Vishal Bodwani, Ben Renow-Clarke, Arijit Sarkar, 和 Lucy Wan

第一版出版时间: 2019 年 3 月

第二版出版时间: 2020 年 5 月

第三版出版时间: 2021 年 5 月

生产参考号: 3210521

ISBN: 978-1-80107-994-5

由 Packt Publishing Ltd.出版

Livery Place, 35 Livery Street

英国伯明翰,B3 2PB。

献给妈妈和爸爸。没有你们为我所做的一切,这本书是不可能完成的。我爱你们俩。

献给 Kelly。如果没有你,我今天不会是现在的我。

- Nills Franssens

目录

前言 i

序言 1

第一章:基础知识 5

1. 容器和 Kubernetes 简介 7

带我们走到今天的软件演变 9

微服务 9

运行微服务的优点 10

运行微服务的缺点 11

DevOps 12

容器基础 14

容器镜像 16

Kubernetes 作为容器编排平台 20

Kubernetes 中的 Pods 21

Kubernetes 中的部署 22

Kubernetes 中的服务 23

Azure Kubernetes 服务 23

总结 25

2. 开始使用 Azure Kubernetes 服务 27

创建 AKS 集群的不同方式 28

开始使用 Azure 门户 29

创建您的第一个 AKS 集群 29

在 Azure 门户中快速概览您的集群 36

使用 Azure Cloud Shell 访问您的集群 40

部署并检查您的第一个示例应用程序 43

部署示例应用程序 44

总结 52

第二部分:在 AKS 上部署 53

3. 在 AKS 上部署应用程序 55

逐步部署示例访客簿应用程序 57

介绍应用程序 57

部署 Redis 主节点 58

检查部署情况 62

带有 ConfigMap 的 Redis 主节点 64

完成部署示例访客簿应用程序 71

暴露 Redis 主节点服务 72

部署 Redis 副本 75

部署并暴露前端 77

访客簿应用程序的实际运行 84

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

使用 Helm 安装 WordPress 86

总结 94

4. 构建可扩展应用程序 95

扩展您的应用程序 96

手动扩展您的应用程序 97

扩展访客簿前端组件 100

使用 HPA 102

扩展您的集群 107

手动扩展您的集群 107

使用集群自动扩展器扩展集群 109

升级您的应用程序 112

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

使用 kubectl edit 升级应用程序 118

使用 kubectl patch 升级应用程序 119

使用 Helm 升级应用程序 122

摘要 126

5. 处理 AKS 中常见的故障 127

处理节点故障 128

解决资源不足失败 135

修复存储挂载问题 139

启动 WordPress 安装 140

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

摘要 153

6. 使用 HTTPS 保护你的应用程序 155

将 Azure 应用程序网关设置为 Kubernetes 入口 156

创建新的应用程序网关 157

设置 AGIC 160

为 guestbook 应用程序添加入口规则 161

为入口添加 TLS 165

安装 cert-manager 166

安装证书颁发机构 168

创建 TLS 证书并保护入口 169

摘要 176

7. 监控 AKS 集群和应用程序 177

用于监控应用程序的命令 178

kubectl get 命令 179

kubectl describe 命令 181

调试应用程序 186

就绪和活性探针 196

构建两个 Web 容器 197

实验活性探针和就绪探针 201

Kubernetes 报告的指标 205

节点状态和消耗 205

Pod 消耗 207

使用 AKS 诊断工具 210

Azure Monitor 指标和日志 213

AKS 洞察 213

摘要 226

第三部分:保护你的 AKS 集群和工作负载 227

8. AKS 中的基于角色的访问控制 229

Kubernetes 中的 RBAC 解析 230

在 AKS 集群中启用 Azure AD 集成 232

在 Azure AD 中创建用户和组 235

在 AKS 中配置 RBAC 240

验证用户的 RBAC 245

总结 250

9. AKS 中的 Azure Active Directory 管理的 Pod 身份 251

Azure AD 管理的 Pod 身份概述 253

使用 Azure AD 管理的 Pod 身份设置新集群 256

将身份链接到集群 258

使用托管身份的 Pod 262

总结 271

10. 在 AKS 中存储密钥 273

Kubernetes 中的不同密钥类型 274

在 Kubernetes 中创建密钥 275

从文件创建密钥 275

使用 YAML 文件手动创建密钥 279

使用 kubectl 使用字面量创建通用密钥 281

使用密钥 282

将密钥作为环境变量 283

将密钥作为文件使用 285

安装 Azure Key Vault 提供程序用于 Secrets Store CSI 驱动程序 289

创建托管身份 291

创建 Key Vault 294

安装 Key Vault 的 CSI 驱动程序 300

使用 Azure Key Vault 提供程序用于 Secrets Store CSI 驱动程序 301

将 Key Vault 密钥挂载为文件 301

使用 Key Vault 密钥作为环境变量 305

总结 310

11. AKS 中的网络安全 311

AKS 中的网络和网络安全 312

控制平面网络 312

工作负载网络 315

控制平面网络安全 317

使用授权的 IP 范围保护控制平面 317

使用私有集群保护控制平面 321

工作负载网络安全 330

使用内部负载均衡器保护工作负载网络 330

使用网络安全组保护工作负载网络 336

使用网络策略保护工作负载网络 343

摘要 352

第四部分:与 Azure 托管服务集成 353

12. 将应用程序连接到 Azure 数据库 355

Azure 服务操作员 356

什么是 ASO? 357

在集群上安装 ASO 359

创建新的 AKS 集群 359

创建托管身份 361

创建密钥保管库 367

在集群上设置 ASO 370

使用 ASO 部署 Azure MySQL 数据库 373

使用 MySQL 数据库创建应用程序 380

摘要 387

13. Kubernetes 的 Azure 安全中心 389

为 Kubernetes 设置 Azure 安全中心 391

部署违规工作负载 396

使用 Azure 安全得分分析配置 403

使用 Azure Defender 中和威胁 415

摘要 428

14. 无服务器函数 429

各种函数平台 431

设置先决条件 433

Azure 容器注册表 433

创建虚拟机 436

创建一个 HTTP 触发的 Azure 函数 442

创建队列触发的函数 447

创建队列 448

创建队列触发的函数 451

功能的扩展测试 458

摘要 461

15. AKS 的持续集成与持续部署 463

容器和 Kubernetes 的 CI/CD 流程 464

设置 Azure 和 GitHub 466

设置 CI 流水线 473

设置 CD 流水线 485

总结 494

最终思考 495

索引 497

前言

关于

本节简要介绍了作者和审阅者、本书的内容、你需要掌握的技术技能,以及完成所有主题所需的硬件和软件。

Azure 上的 Kubernetes 实战 – 第三版

容器和 Kubernetes 容器通过实现更高效的版本控制、增强的安全性和可移植性,促进了云部署和应用程序开发。

本书第三版更新了关于基于角色的访问控制、Pod 身份、存储机密和 AKS 中网络安全的章节,首先介绍了容器、Kubernetes 和 Azure Kubernetes 服务AKS),并引导你通过不同方式部署 AKS 集群。接着,你将深入了解 Kubernetes 的具体细节,通过在 AKS 上部署一个示例的留言本应用,并使用 Helm 安装复杂的 Kubernetes 应用。通过真实世界的示例,你还将学会如何扩展你的应用和集群。

随着学习的深入,你将学会如何克服 AKS 中常见的挑战,并使用 HTTPS 来保护你的应用。你还将学会如何通过专门的安全章节保护你的集群和应用。在最后一部分,你将了解高级集成,学习如何在 AKS 上创建 Azure 数据库、运行无服务器功能,并通过 GitHub Actions 将 AKS 与持续集成和持续交付(CI/CD)管道进行集成。

到本书结束时,你将能够熟练地在 Microsoft Azure 上部署容器化的工作负载,且管理开销最小。

关于作者

Nills Franssens 是一位技术爱好者,也是多种开源技术的专家。自 2013 年以来,他一直从事公共云技术的工作。

在他目前担任微软首席云解决方案架构师的职位上,他与微软的战略客户合作,推动他们的云采纳。他曾与多个客户合作,帮助他们将应用程序迁移到 Azure 上的 Kubernetes 运行。Nills 的专业领域包括 Kubernetes、网络和 Azure 存储。

当他不工作时,你可以找到 Nills 和他的妻子 Kelly 以及朋友们一起玩桌游,或在加利福尼亚州圣荷西的多个小径上跑步。

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

他在软件开发方面拥有多年的经验,涉猎广泛,包括网络、存储、医学影像,目前主要从事 DevOps 工作。他曾致力于开发专门为医学影像需求量身定制的可扩展存储设备,并帮助架构基于微服务的云原生解决方案,以交付模块化的 AngularJS 应用程序。他在多个活动中发表过关于将 AI 和机器学习融入 DevOps 以促进大企业学习文化的演讲。

他曾帮助高度监管的大型医疗企业团队采用现代敏捷/DevOps 方法,包括 "You build it, you run it" 模型。他制定并领导了 DevOps 路线图的实施,将传统团队转变为能够无缝采用以安全和质量为先的 CI/CD 工具的团队。他拥有吉因迪工程学院的工学学士学位,以及马里兰大学帕克分校的理学硕士学位。

Gunther Lenz 是 Varian 技术办公室的高级总监。他是一位创新的软件研发领导者、架构师、MBA、出版作者、公众演讲者和战略技术远见者,拥有超过 20 年的经验。

他有着成功领导超过 50 人的大型、创新性和转型性软件开发和 DevOps 团队的显著经验,专注于持续改进。他通过利用突破性的过程、工具和技术(如云、DevOps、精益/敏捷、微服务架构、数字化转型、软件平台、人工智能和分布式机器学习)在整个软件产品生命周期中领导和定义分布式团队。

他曾获得微软软件架构领域的微软最有价值专家(MVP)称号(2005-2008)。Gunther 已出版两本书:《.NET – 完整开发周期》和《.NET 实践软件工厂》。

关于评审人员

理查德·胡珀,网名 PixelRobots,居住在英国纽卡斯尔,他是微软 Azure MVP 和微软认证讲师(MCT),现担任总部位于荷兰的公司 Intercept 的 Azure 架构师。他在 IT 行业拥有超过 15 年的职业经验。尽管他职业生涯一直与微软技术打交道,但也曾涉足 Linux。他对 Azure 和 Azure Kubernetes 服务(AKS)充满热情,并每天使用它们。在业余时间,他喜欢通过博客、播客、视频以及各种技术分享自己的知识,帮助他人,希望能为 Azure 之路上的人们提供帮助。理查德热衷于博客写作和学习,这使他每周都有新的发现。当有机会成为 AKS 书籍的技术审阅员时,他毫不犹豫地接受了这个挑战!你可以在 Twitter 上找到他,用户名是 @pixel_robots。

Swaminathan Vetri(Swami)在 Maersk 技术中心班加罗尔担任架构师,使用 Azure 的各种 PaaS 产品和 Kubernetes 构建云原生应用程序。自 2016 年以来,他因在开发者社区的技术贡献而被微软评为 MVP - 开发者技术专家。除了写技术博客,他还经常在本地开发者会议、用户小组会议、聚会等场合发言,涉及的主题包括 .NET、C#、Docker、Kubernetes、Azure DevOps、GitHub Actions 等等。作为一个持续学习者,他热衷于与社区分享他的知识。您可以在 Twitter 和 GitHub 上关注他,用户名是 @svswaminathan。

学习目标

  • 在生产环境中计划、配置和运行容器化应用程序。

  • 使用 Docker 构建容器中的应用程序,并将其部署到 Kubernetes 上。

  • 监控 AKS 集群和应用程序。

  • 使用 Azure Monitor 监控 Kubernetes 中的基础设施和应用程序。

  • 使用 Azure 原生安全工具保护您的集群和应用程序。

  • 将应用程序连接到 Azure 数据库。

  • 使用 Azure 容器注册表安全地存储容器镜像。

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

  • 将 Kubernetes 与多个 Azure PaaS 服务集成,例如数据库、Azure 安全中心和 Functions。

  • 使用 GitHub Actions 执行持续集成和持续交付到您的集群。

受众

如果您是一个有抱负的 DevOps 专业人员、系统管理员、开发人员或网站可靠性工程师,想要学习如何最大限度地利用容器和 Kubernetes,那么本书适合您。

方法

本书专注于实践经验和理论知识的良好平衡结合,并伴有与 Kubernetes 平台上专业人员工作直接相关的真实世界场景。每一章都明确设计,帮助您在实践中最大化应用所学。

硬件和软件要求

硬件要求

为了获得最佳实验体验,我们推荐以下硬件配置:

  • 处理器:Intel Core i5 或同等配置

  • 内存:4GB RAM(推荐 8 GB)

  • 存储:35 GB 可用空间

软件要求

我们还建议您提前配置以下软件:

  • 一台运行 Linux、Windows 10 或 macOS 操作系统的计算机

  • 需要一个互联网连接和网页浏览器,以便您能够连接到 Azure。

约定

文本中的代码词汇、数据库名称、文件夹名称、文件名和文件扩展名如下所示。

front-end-service-internal.yaml 文件包含使用 Azure 内部负载均衡器创建 Kubernetes 服务的配置。以下代码是该示例的一部分:

1   apiVersion: v1
2   kind: Service
3   metadata:
4     name: frontend
5     annotations:
6       service.beta.kubernetes.io/azure-load-balancer-internal: "true"
7     labels:
8       app: guestbook
9       tier: frontend
10  spec:
11    type: LoadBalancer
12    ports:
13    - port: 80
14    selector:
15      app: guestbook
16      tier: frontend

下载资源

本书的代码包可在 https://github.com/PacktPublishing/Hands-on-Kubernetes-on-Azure-Third-Edition 下载。

我们还有来自我们丰富书籍和视频目录的其他代码包,可以在 https://github.com/PacktPublishing/ 查找。快去看看吧!

前言

欢迎!通过拿起这本书,你已经表明自己对两个事物感兴趣:Azure 和 Kubernetes,这两个都深深地吸引着我。我很高兴你能与我们一同踏上云原生之旅。不论你是刚接触 Azure、Kubernetes,还是两者都不熟悉,我相信,在你探索 Azure Kubernetes Service (AKS)时,你将发现许多新的方式来转变你的应用程序、取悦客户、满足业务日益增长的需求,或者简单地学习一些新的技能,帮助你实现职业目标。不论你开始这段旅程的理由是什么,我们都迫不及待地想在你前行的路上提供帮助,并期待看到你能在 Kubernetes 和 Azure 上构建出什么。

Kubernetes 在 Azure 上的旅程本身也是一段激动人心的经历。在过去的几年里,AKS 成为了 Azure 历史上增长最快的服务。我们正处于 Azure 本身超大规模增长的拐点,同时也处于 Kubernetes 上运行的应用程序的“曲棍球棒”增长期。将这两者结合起来,过去几年充满了激动人心(又繁忙)的时光。

能够为我们的客户和用户交付成功,真是令人激动。但究竟是 Azure 和 Kubernetes 中的哪些特性促成了客户的成功呢?虽然有时看起来像是魔法,但事实上,Azure 和 Kubernetes 并没有什么真正的魔力。我们的客户所看到的价值、成功和转型与他们的需求以及这些技术如何帮助实现这些目标密切相关。

在过去十年,尤其是去年,我们看到,随着世界变化的速度加快,敏捷和适应能力成为我们每个人的关键能力。Kubernetes 通过引入诸如容器和容器镜像的概念,以及更高层次的概念,如服务和部署,使得这种敏捷性成为可能,这些自然推动我们朝着解耦的微服务架构前进。当然,你可以在没有 Kubernetes 的情况下构建微服务应用程序,但 API 和设计模式的自然趋势是将你推向这种架构。可以说,微服务是 Kubernetes 的引力井。然而,值得注意的是,微服务并不是在 Kubernetes 上运行应用程序的唯一方式。许多客户发现,将他们的传统应用程序迁移到 Kubernetes 上,并将现有应用程序的管理与新的云原生实现的开发相结合,带来了巨大的好处。

随着越来越多的人开始将生活更多地搬到线上,我们所构建的所有服务的关键性发生了剧变。如今,维护时间计划停机已经无法接受。我们生活在一个 24x7 的世界中,应用程序需要随时可用,即使在我们构建、修改和调整它们的时候也是如此。在这方面,Kubernetes 和 Azure 提供了您构建可靠应用所需的工具。Kubernetes 提供健康检查功能,如果应用程序崩溃,它会自动重启应用程序;提供零停机时间发布的基础设施;以及能够根据客户负载自动扩展的技术。除了这些功能外,Azure 还提供了基础设施,可以在不影响集群中运行的应用程序的情况下对 Kubernetes 本身进行升级,并对集群本身进行自动扩展,以提供更多的容量来满足不断增长的应用需求,同时具备弹性以将集群调整到最有效的形状。

除了这些核心功能,使用 AKS 还可以访问更广泛的云原生生态系统。在 Cloud Native Compute FoundationCNCF)生态系统中,有无数工程师和项目可以帮助您更快、更可靠地构建应用程序。作为这些项目的领导者和贡献者,Azure 提供了集成支持,并为您提供访问世界上一些最优秀开源软件的途径,包括 Helm、Gatekeeper、Flux 等。

但事实上,在 Kubernetes 上构建任何应用程序,涉及的远不止 Kubernetes 组件。微软拥有一套独特的工具,与 AKS 集成,提供无缝的端到端体验。从 GitHub 开始,那里是全球开发者聚集和协作的地方,再到 Visual Studio Code,开发人员在这里构建软件本身,再到 Azure Monitor 和 Azure Security Center 等工具,用于保持应用程序的健康和安全,正是 Azure 的综合能力使得 AKS 成为一个理想的环境,助力您的应用蓬勃发展。当您将其与 Azure 在全球范围内的领先云端基础设施相结合,Azure 提供的托管 Kubernetes 部署覆盖了比任何人都更多的地区,您可以看到,AKS 帮助企业从初创阶段到全球企业级的扩展和发展需求。

感谢您选择 Azure 和 Kubernetes!我很高兴您来到这里,希望您能享受学习 Kubernetes 和 Azure 所提供的一切。

– Brendan Burns Kubernetes 联合创始人兼微软公司副总裁

第一部分:基础知识

在本书的第一节中,我们将介绍你需要理解的基本概念,以便跟随本书中的示例进行操作。

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

当你完成本节内容时,你将具备容器和 Kubernetes 的基础知识,并且能够在 Azure 上启动并运行一个 Kubernetes 集群,从而跟随本书中的示例进行操作。

本节包含以下章节:

  • 第一章,容器和 Kubernetes 简介

  • 第二章,开始使用 Azure Kubernetes 服务

第二章:1. 容器和 Kubernetes 简介

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

Kubernetes 建立在 Docker 容器革命的成功基础之上。Docker 既是一个公司,也是一种技术。作为一种技术,Docker 是创建和运行软件容器(称为 Docker 容器)的最常见方式。容器是一种打包软件的方式,使得能够在任何平台上运行这些软件,从您的笔记本电脑到数据中心的服务器,再到公共云中运行的集群。

尽管核心技术是开源的,Docker 公司专注于通过多种商业提供来简化开发者的复杂性。

Kubernetes 将容器技术推向了新的高度。Kubernetes 是一个容器编排器。容器编排器是一个软件平台,使得在成千上万台机器上运行成千上万个容器变得非常容易。它自动化了部署、运行和扩展应用程序所需的许多手动任务。编排器负责在正确的机器上调度正确的容器运行。它还负责健康监控、故障转移以及扩展您部署的应用程序。

Docker 使用的容器技术和 Kubernetes 都是开源软件项目。开源软件允许来自多家公司的开发者共同协作开发单一软件。Kubernetes 本身有来自微软、谷歌、红帽、VMware 等公司的贡献者。

三大公共云平台——Azure,亚马逊网络服务AWS),以及谷歌云平台GCP)——都提供了托管的 Kubernetes 服务。这些托管服务因其几乎无限的计算能力和易用性而在市场上引起了极大的关注,使得构建和部署大规模应用变得非常容易。

Azure Kubernetes 服务AKS)是 Azure 的 Kubernetes 托管服务。它降低了构建和管理 Kubernetes 集群的复杂性。在本书中,您将学习如何使用 AKS 来运行您的应用程序。每一章都将介绍新的概念,并通过本书中的许多示例进行应用。

然而,作为用户,了解支持 AKS 的技术仍然非常有用。我们将在本章中探讨这些基础知识。您将了解 Linux 进程及其与 Docker 和容器的关系。您将看到各种进程如何很好地适配到容器中,以及容器如何很好地适配到 Kubernetes 中。

本章介绍了 Docker 的基础概念,以便你可以开始你的 Kubernetes 之旅。本章还简要介绍了帮助你构建容器、实现集群、进行容器编排并在 AKS 上排除故障的基本知识。掌握本章内容的基本知识,将揭开在 AKS 上构建经过认证、加密和高可扩展性应用所需工作的神秘面纱。在接下来的几章中,你将逐步构建可扩展和安全的应用程序。

本章将涵盖以下主题:

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

  • 容器基础

  • Kubernetes 基础

  • AKS 基础

本章的目的是介绍基础知识,而不是提供一个详细的信息来源来描述 Docker 和 Kubernetes。首先,我们将回顾软件是如何演变,最终带我们到今天的。

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

有两个主要的软件开发演变使得容器和 Kubernetes 得以流行。一个是微服务架构风格的采用。微服务允许将一个应用程序构建成由多个小服务组成,每个服务承担一个特定功能。另一个推动容器和 Kubernetes 发展的演变是 DevOps。DevOps 是一组文化实践,允许人们、流程和工具更快、更频繁、更可靠地构建和发布软件。

虽然你可以在不使用微服务或 DevOps 的情况下使用容器和 Kubernetes,但这些技术最广泛的应用是在采用 DevOps 方法论的微服务部署中。

在本节中,我们将讨论这两种演变,从微服务开始。

微服务

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

客户端-服务器编程模型已经演变成分布式系统。分布式系统与传统的客户端-服务器模型不同,因为它们在多个不同的系统上运行多个不同的应用程序,并且所有系统都是相互连接的。

如今,微服务架构在开发分布式系统时已变得非常常见。基于微服务的应用程序由一组服务组成,这些服务共同协作以构成应用程序,而单独的服务本身可以独立构建、测试、部署和扩展。该风格有许多优点,但也存在一些缺点。

微服务架构的一个关键特点是每个服务只执行一个核心功能。每个服务专注于单一的业务功能。不同的服务协同工作,形成完整的应用程序。这些服务通过网络通信协作,通常使用 HTTP REST API 或 gRPC:

从主机到客户端-服务器,再到分布式系统,再到微服务的演变

图 1.1:标准微服务架构

这种架构方法通常被容器和 Kubernetes 运行的应用程序采纳。容器作为单个服务的打包格式,而 Kubernetes 则是部署和管理多个协同工作的服务的协调器。

在深入了解容器和 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,或用于持续集成和持续交付的 Jenkins,以及完整的 DevOps 平台,如 GitLab。微软运营着两个使客户能够采用 DevOps 实践的解决方案:Azure DevOps 和 GitHub。Azure DevOps 是一个支持 DevOps 过程所有阶段的服务套件。GitHub 是一个独立的平台,用于支持 DevOps 软件开发。GitHub 被公认为领先的开源软件开发平台,托管着超过 4000 万个开源项目。

微服务和 DevOps 通常与容器和 Kubernetes 结合使用。现在我们已经了解了微服务和 DevOps 的基本概念,我们将继续本章的内容,介绍容器的基本概念,接着是 Kubernetes 的基本概念。

容器基础

一种容器技术自 1970 年代以来就存在于 Linux 内核中。今天容器背后的技术被称为cgroups控制组的缩写),它在 2006 年由 Google 引入到 Linux 内核中。Docker 公司在 2013 年通过引入一种简便的开发者工作流程,使这一技术普及开来。虽然“Docker”一词可以指代公司或技术,但通常我们用 Docker 指的是技术本身。

注意事项

尽管 Docker 技术是构建和运行容器的流行方式,但它并不是唯一的方式。存在许多替代方案,用于构建或运行容器。其中一个替代方案是 containerd,它也是 Kubernetes 使用的容器运行时。

Docker 作为一种技术,既是打包格式也是容器运行时。打包是一个过程,它将应用程序及其依赖项(如二进制文件和运行时)打包在一起。运行时则指向运行容器镜像的实际过程。

Docker 架构中有三个重要组成部分:客户端、守护进程和注册中心:

  • Docker 客户端是一个客户端工具,用于与 Docker 守护进程进行交互,可以在本地或远程运行。

  • Docker 守护进程是一个长时间运行的进程,负责构建容器镜像并运行容器。Docker 守护进程可以运行在本地计算机或远程计算机上。

  • Docker 注册中心是存储 Docker 镜像的地方。有公共注册中心,如 Docker Hub,其中包含公共镜像,也有私有注册中心,如Azure 容器注册中心ACR),你可以使用它来存储自己的私有镜像。如果本地没有镜像,Docker 守护进程可以从注册中心拉取镜像:

Docker 架构中的三个重要组件:客户端、守护进程和注册中心

图 1.2:Docker 架构基础

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

注意

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

容器镜像

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

在接下来的示例中,我们将从公共 Docker Hub 仓库拉取一个镜像并运行实际的容器。你可以按照这些说明在 Docker Labs 中运行这个示例,正如我们在上一节中介绍的那样:

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

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

从公共 Docker Hub 仓库拉取镜像并运行实际的容器

图 1.3:在 Docker Labs 中运行容器的示例

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

在前面的示例中,你学习了如何在不先构建镜像的情况下运行容器。然而,通常你会想要构建自己的镜像。为此,你需要使用一个 Dockerfile。Dockerfile 包含了一系列 Docker 会按照步骤来构建镜像的指令。这些指令可以包括从添加文件到安装软件或设置网络等操作。

在下一个示例中,你将构建一个自定义 Docker 镜像。这个自定义镜像将在鲸鱼输出中显示励志名言。以下的 Dockerfile 将用于生成这个自定义镜像。你将在你的 Docker 练习环境中创建它:

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

这个 Dockerfile 包含四行指令。第一行会告诉 Docker 使用哪个镜像作为新镜像的基础镜像。接下来的两行是运行命令,用于向镜像添加新功能,在这个例子中是更新apt仓库并安装一个名为fortunes的应用程序。fortunes是一个小型命令行工具,可以生成励志名言。我们将使用它来在输出中包含名言,而不是用户输入。最后,CMD命令告诉 Docker 在基于此镜像运行容器时执行哪个命令。

通常,你会将 Dockerfile 保存在一个名为Dockerfile的文件中,不带扩展名。要构建镜像,你需要执行docker build命令并指向你创建的 Dockerfile。在构建 Docker 镜像时,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.4的输出。然而,你可能会看到不同的智能引号。这是因为fortunes应用程序会生成不同的引言。如果你多次运行容器,你将看到不同的引言,如图 1.4所示:

使用 docker run 命令运行自定义容器

图 1.4:运行自定义容器

这就是我们对容器的概述和演示。在这一部分,你从现有的容器镜像开始,并在 Docker Labs 上启动它。随后,你又进一步操作,构建了你自己的容器镜像,并使用该镜像启动了容器。你现在已经了解了构建和运行容器的步骤。在接下来的部分,我们将介绍 Kubernetes。Kubernetes 允许你在大规模环境下运行多个容器。

Kubernetes 作为容器编排平台

构建和运行一个单一容器似乎很简单。然而,当你需要在多个服务器上运行多个容器时,事情可能会变得复杂。这时,容器编排器可以派上用场。容器编排器负责调度容器在服务器上运行,在容器失败时重新启动容器,当主机变得不健康时,将容器迁移到新的主机,等等。

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

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

注意

尽管 Kubernetes 曾经支持 Docker 作为容器运行时,但在 Kubernetes 1.20 版本中,这一支持已被弃用。在 AKS 中,containerd 从 Kubernetes 1.19 开始成为默认的容器运行时。

在本书中,您将构建多个在 Kubernetes 中运行容器的示例,并深入了解 Kubernetes 中的不同对象。在这一介绍章节中,您将学习 Kubernetes 中三个基本对象,这三个对象在每个应用程序中几乎都会出现:Pod、Deployment 和 Service。

Kubernetes 中的 Pod

在 Kubernetes 中,Pod 是基本的调度元素。Pod 是一个或多个容器的组合。这意味着 Pod 可以包含单个容器或多个容器。在创建单容器 Pod 时,您可以将容器和 Pod 互换使用。然而,Pod 这个术语仍然是首选术语,并且是本书中使用的术语。

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

在设计方面,您应该只将需要紧密集成的容器放在同一个 Pod 中。想象一下以下情况:您有一个不支持 HTTPS 的旧 Web 应用程序。您希望将该应用程序升级以支持 HTTPS。您可以创建一个包含旧 Web 应用程序的 Pod,并在其中添加另一个容器,该容器将为该应用程序执行 传输层安全性TLS)卸载,如 图 1.5 所示。用户将通过 HTTPS 连接到您的应用程序,而中间的容器将 HTTPS 流量转换为 HTTP:

在单个 Pod 中使用两个容器来支持 HTTPS 流量

图 1.5:一个执行 HTTPS 卸载的多容器 Pod 示例

注意

这种设计原则被称为 sidecar(旁车模式)。微软提供了一本免费的电子书,介绍了多种多容器 Pod 设计以及分布式系统的设计(azure.microsoft.com/resources/designing-distributed-systems/)。

无论是单容器 Pod 还是多容器 Pod,Pod 都是一个临时资源。这意味着,Pod 可以在任何时候终止并在另一个节点上重新启动。当发生这种情况时,Pod 中存储的状态将会丢失。如果您需要在应用程序中存储状态,则需要将状态存储在外部存储中,如外部磁盘或文件共享,或者将状态存储在 Kubernetes 外部的数据库中。

Kubernetes 中的部署

Kubernetes 中的部署(deployment)提供了一个关于 Pod 的功能层。它允许你根据相同的定义创建多个 Pod,并且能够轻松地对已部署的 Pod 进行更新。部署还帮助你对应用程序进行扩展,甚至可能实现应用程序的自动扩展。

在内部,部署会创建一个副本集(ReplicaSet),该副本集会创建你请求的副本 Pod。副本集是 Kubernetes 中的另一种对象。副本集的目的是在任何给定时间内维护一个稳定的副本 Pod 集合。如果你对部署进行更新,Kubernetes 会创建一个新的副本集,其中包含更新后的 Pod。默认情况下,Kubernetes 会执行滚动升级到新版本。这意味着它会先启动一些新的 Pod,验证这些 Pod 是否正确运行,如果确认无误,Kubernetes 将终止旧的 Pod,并继续这个循环,直到只剩下新的 Pod 在运行:

展示部署创建副本集,副本集又创建多个 Pod 之间的关系

图 1.6:部署、副本集与 Pod 之间的关系

Kubernetes 中的服务

Kubernetes 中的服务(service)是一个网络层的抽象。这使得你能够通过一个单一的 IP 地址和 DNS 名称暴露多个 Pod。

Kubernetes 中的每个 Pod 都有自己的私有 IP 地址。理论上,你可以使用这个私有 IP 地址连接到你的应用程序。然而,正如前面所提到的,Kubernetes 中的 Pod 是临时的,这意味着它们可能会被终止并移动,从而改变它们的 IP 地址。通过使用服务,你可以通过一个单一的 IP 地址连接到你的应用程序。当 Pod 从一个节点移动到另一个节点时,服务确保流量被路由到正确的端点。如果有多个 Pod 在一个服务后面提供流量,这些流量将会在不同的 Pod 之间进行负载均衡。

在本节中,我们介绍了 Kubernetes 以及与 Kubernetes 相关的三个基本对象。在下一节中,我们将介绍 AKS。

Azure Kubernetes 服务

AKS 使得创建和管理 Kubernetes 集群更加容易。

一个典型的 Kubernetes 集群由若干个主节点和若干个工作节点组成。在 Kubernetes 中,一个节点相当于一台服务器或一台虚拟机VM)。主节点包含 Kubernetes API 以及一个包含集群状态的数据库。工作节点则是实际运行工作负载的机器。

AKS 使得创建集群变得更加简单。当你创建一个 AKS 集群时,AKS 会为你设置 Kubernetes 的主节点。然后,AKS 会在你的订阅中创建一个或多个虚拟机规模集VMSS),并将这些 VMSS 中的虚拟机转化为你网络中 Kubernetes 集群的工作节点。在 AKS 中,你可以选择使用免费的 Kubernetes 控制平面,或者为带有财务担保 SLA 的控制平面付费。在这两种情况下,你还需要支付托管工作节点的虚拟机费用:

在 AKS 中调度节点上的 pod

图 1.7:在 AKS 中调度 pod

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

此外,AKS 增加了许多功能,使得集群管理更加容易。AKS 包含逻辑来升级集群到更新的 Kubernetes 版本。它还可以通过添加或移除节点来轻松地扩展集群。

AKS 还提供了一些集成功能,使得操作更加简便。AKS 集群可以与Azure Active DirectoryAzure AD)集成,以简化身份管理和基于角色的访问控制RBAC)的配置过程。RBAC 是定义哪些用户可以访问资源以及他们对资源可以执行哪些操作的配置过程。AKS 还可以轻松与 Azure Monitor for containers 集成,使得监控和故障排除应用程序变得更加简单。你将在本书中了解所有这些功能。

总结

在本章中,你了解了容器和 Kubernetes 的概念。你运行了多个容器,首先使用了现有镜像,然后使用自己构建的镜像。演示后,你了解了三个关键的 Kubernetes 对象:pod、deployment 和 service。

这为接下来的章节提供了背景,在这些章节中,你将使用 Microsoft AKS 部署容器化应用程序。你将看到,Microsoft 提供的 AKS 服务如何通过处理许多管理和操作任务,简化了部署过程,这些任务如果你自己管理和操作 Kubernetes 基础设施,则需要亲自完成。

在下一章中,你将使用 Azure 门户创建你的第一个 AKS 集群。

第三章:2. 开始使用 Azure Kubernetes Service

正确、安全地安装和维护 Kubernetes 集群是困难的。幸运的是,所有主要的云服务提供商,如AzureAmazon Web ServicesAWS)和Google Cloud PlatformGCP),都提供了安装和维护集群的支持。在本章中,您将通过 Azure 门户导航,启动自己的集群并运行示例应用程序。所有这些操作都将在您的浏览器中完成。

本章将涵盖以下主题:

  • 创建一个新的 Azure 免费帐户

  • 创建并启动您的第一个集群

  • 部署并检查您的第一个示范应用程序

我们首先来看看创建Azure Kubernetes ServiceAKS)集群的不同方式,然后运行我们的示例应用程序。

创建 AKS 集群的不同方式

在本章中,您将使用 Azure 门户来部署 AKS 集群。然而,也有多种方式可以创建 AKS 集群:

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

  • 使用 Azure CLI:Azure 命令行界面CLI)是一个跨平台的 CLI 工具,用于管理 Azure 资源。它允许您脚本化集群部署,并且可以集成到其他脚本中。

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

  • 使用 ARM 模板Azure 资源管理器ARM)模板是 Azure 原生的一种部署 Azure 资源的方法,使用基础设施即代码IaC)。您可以声明式地部署集群,允许您创建一个模板,供多个团队重用。

  • 使用 Terraform for 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) 中创建服务主体。

本书中的所有示例都已在免费试用账户上验证。

我们将直接开始创建我们的 AKS 集群。通过这样做,我们也将熟悉 Azure 门户。

创建你的第一个 AKS 集群

首先,浏览到 Azure 门户 portal.azure.com。在 Azure 门户顶部的搜索栏中输入关键词 aks。在搜索结果中点击服务类别下的 Kubernetes 服务:

通过 Azure 门户搜索栏搜索 AKS

图 2.1:使用搜索栏搜索 AKS

这将带你进入门户中的 AKS 面板。正如你可能预料的那样,你还没有任何集群。继续点击 + 添加 按钮,并选择 + 添加 Kubernetes 集群选项:

点击添加按钮和添加 Kubernetes 集群按钮以开始集群创建过程

图 2.2:点击 + 添加 按钮和 + 添加 Kubernetes 集群按钮以开始集群创建过程

注意

在创建 AKS 集群时,有很多选项可以配置。对于第一次创建集群,我们建议使用门户中的默认设置,并在本示例中遵循我们的命名规范。以下设置已经过我们测试,适用于免费账户。

这将带你进入创建向导,开始创建你的第一个 AKS 集群。这里的第一步是创建一个新的资源组。点击创建新建,给你的资源组命名,然后点击 OK。如果你想跟随本书中的示例,请将资源组命名为 rg-handsonaks

提供创建新资源组的必要信息

图 2.3:创建新资源组

接下来,我们将提供集群的详细信息。给你的集群命名——如果你想跟随书中的示例,请将其命名为 handsonaks。我们将在书中使用的区域是 (US)West US 2,但你可以选择任何靠近你位置的其他区域。如果你选择的区域支持可用性区域,请取消选择所有区域。

选择 Kubernetes 版本——截至写作时,版本 1.19.6 是最新的受支持版本;如果该版本对你不可用,也无需担心。Kubernetes 和 AKS 发展非常迅速,经常会推出新版本:

注意

对于生产环境,建议在可用区中部署集群。然而,由于我们正在部署一个小型集群,不使用可用区对于本书中的示例来说是最合适的。

输入集群名称并选择区域和 Kubernetes 版本

图 2.4:提供集群详细信息

接下来,将节点数量更改为 2。为了本书中的演示,默认的Standard DS2 v2节点大小足够。这将使您的集群大小类似于图 2.5所示的样子:

将节点数量更新为 2 并验证 Standard DS2 v2 机器大小

图 2.5:更新的节点大小和节点数量

注意

您的免费帐户有四核限制,如果使用默认配置,将会超过限制。

第一个窗格的最终视图应类似于图 2.6。有多个配置窗格,您无需为本书中将使用的演示集群更改这些配置。既然您已准备好,点击“审核+创建”按钮进行最终审核并创建您的集群:

基础选项卡下所有配置的最终视图

图 2.6:设置集群配置

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

在“审核+创建”选项卡下查看所有集群配置

图 2.7:集群配置的最终验证

部署集群大约需要 10 分钟。部署完成后,您可以查看如图 2.8所示的部署详情:

集群成功部署后的部署详情概览

图 2.8:集群成功部署后的部署详情

如果您遇到配额限制错误,如图 2.9所示,请检查设置并重试。确保选择Standard DS2_v2节点大小,并且节点数量为 2:

由于配额限制错误,重新部署一个较小大小的集群

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

进入下一部分,我们将快速查看您的集群;点击如图 2.8所示的“转到资源”按钮。这将把您带到 Azure 门户中的 AKS 集群仪表板。

在 Azure 门户中快速概览您的集群

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

在 Azure 门户中查看 AKS 窗格概览

图 2.10:Azure 门户中的 AKS 窗格

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

Kubernetes 资源部分为您提供关于运行在集群上的工作负载的洞察。例如,您可以查看正在运行的部署和正在运行的 pods。它还允许您在集群上创建新的资源。我们将在本章稍后使用此部分,在您将第一个应用程序部署到 AKS 后。

在节点池面板中,您可以通过添加或删除节点来扩展现有的节点池(即集群中的节点或服务器),实现向上或向下扩展。您可以添加一个新的节点池,可能使用不同的虚拟机规格,还可以单独升级您的节点池。在图 2.11中,您可以看到左上角的 + 添加节点池 选项,如果选择了您的节点池,顶部的 升级 和 扩展 选项也会变得可用:

在节点池面板中添加、扩展和升级节点池

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

集群配置面板中,您可以指示 AKS 将控制平面升级到较新版本。通常,在 Kubernetes 升级中,您首先升级控制平面,然后分别升级各个节点池。该面板还允许您启用基于角色的访问控制RBAC)(默认启用),并可选择将集群与 Azure AD 集成。您将在第八章,AKS 中的基于角色的访问控制中了解更多有关 Azure AD 集成的信息:

使用升级面板升级 API 服务器的 Kubernetes 版本

图 2.12:使用升级面板升级 API 服务器的 Kubernetes 版本

最后,Insights 面板允许您监控集群基础设施和运行在集群上的工作负载。由于您的集群是全新的,因此没有太多数据可以调查。我们将在第七章中返回此部分,监控 AKS 集群和应用程序

使用 Insights 面板监控集群利用率

图 2.13:使用 Insights 面板显示集群利用率

这就是我们对集群和 Azure 门户中一些有趣配置选项的简要概述。在接下来的部分,我们将使用 Cloud Shell 连接到我们的 AKS 集群,并在该集群上启动一个演示应用程序。

通过 Azure Cloud Shell 访问您的集群

一旦部署成功完成,找到靠近搜索栏的小 Cloud Shell 图标,如图 2.14所示,然后点击它:

点击 Cloud Shell 图标以打开 Azure Cloud Shell

图 2.14:点击 Cloud Shell 图标以打开 Azure Cloud Shell

门户会要求您选择 PowerShell 或 Bash 作为默认的 shell 环境。由于我们主要处理 Linux 工作负载,请选择 Bash:

选择 Bash 作为默认代码编辑器

图 2.15:选择 Bash 选项

如果这是您第一次启动 Cloud Shell,系统会要求您创建一个存储账户;请确认并创建它:

为 Cloud Shell 创建新存储账户

图 2.16:为 Cloud Shell 创建新存储账户

在创建存储后,您可能会收到包含挂载存储错误的错误信息。如果发生这种情况,请重启您的 Cloud Shell:

在可能收到挂载存储错误时点击重启按钮

图 2.17:在收到挂载存储错误时点击重启按钮

点击电源按钮。它应该会重启,您应该会看到类似 图 2.18 的界面:

显示已登录 Cloud Shell 的用户

图 2.18:成功启动 Cloud Shell

您可以上下拖动分隔符/分隔线,以查看更多或更少的 Shell 内容:

使用分隔符调整 Cloud Shell 窗口大小

图 2.19:使用分隔符调整 Cloud Shell 大小

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

注意

在 Kubernetes 社区中,关于 kubectl 的正确发音有一些讨论。常见的发音方式是 kube-c-t-lkube-controlkube-cuddle

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

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

注意

本书中,您将经常看到较长的命令分布在多行中,并使用反斜杠符号。这有助于提高命令的可读性,同时仍然允许您复制粘贴。如果您正在输入这些命令,可以安全地忽略反斜杠,并将完整命令输入在同一行中。

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

kubectl get nodes

您应该看到类似 图 2.20 的内容:

 命令的输出

图 2.20:kubectl get nodes 命令的输出

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

部署并检查您的第一个演示应用

既然您已经连接成功,让我们启动您的第一个应用程序。在本节中,您将部署第一个应用,并通过 kubectl 以及稍后通过 Azure 门户检查它。我们先从部署应用开始。

部署演示应用

在这一部分,你将部署你的演示应用程序。为此,你需要写一些代码。在 Cloud Shell 中,有两种方式可以编辑代码。你可以通过命令行工具,如vinano,来编辑代码,或者你可以通过在 Cloud Shell 中输入code命令使用基于 GUI 的代码编辑器。在本书中,示例会主要指导你使用图形编辑器,但你可以自由选择你最熟悉的任何工具。

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

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

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

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

现在你将直接使用Chapter02文件夹中的代码。此时,你不必专注于代码文件中的内容。本章的目标是启动一个集群,并在其上部署一个应用程序。在接下来的章节中,我们将深入探讨 Kubernetes 配置文件的构建方式,以及如何创建你自己的配置文件。

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

code azure-vote.yaml

这是便于你参考的代码示例:

1 	apiVersion: apps/v1
2 	kind: Deployment
3 	metadata:
4 	  name: azure-vote-back
5 	spec:
6 	  replicas: 1
7 	  selector:
8 		matchLabels:
9 		  app: azure-vote-back
10	  template:
11		metadata:
12		  labels:
13			app: azure-vote-back
14		spec:
15		  containers:
16		  - name: azure-vote-back
17			image: redis
18			resources:
19			  requests:
20				cpu: 100m
21				memory: 128Mi
22			  limits:
23				cpu: 250m
24				memory: 256Mi
25			ports:
26			- containerPort: 6379
27			  name: redis
28	---
29	apiVersion: v1
30	kind: Service
31	metadata:
32	  name: azure-vote-back
33	spec:
34	  ports:
35	  - port: 6379
36	  selector:
37		app: azure-vote-back
38	---
39	apiVersion: apps/v1
40	kind: Deployment
41	metadata:
42	  name: azure-vote-front
43	spec:
44	  replicas: 1
45	  selector:
46		matchLabels:
47		  app: azure-vote-front
48	  template:
49		metadata:
50		  labels:
51			app: azure-vote-front
52		spec:
53		  containers:
54		  - name: azure-vote-front
55			image: microsoft/azure-vote-front:v1
56			resources:
57			  requests:
58				cpu: 100m
59				memory: 128Mi
60			  limits:
61				cpu: 250m
62				memory: 256Mi
63			ports:
64			- containerPort: 80
65			env:
66			- name: REDIS
67			  value: "azure-vote-back"
68	---
69	apiVersion: v1
70	kind: Service
71	metadata:
72	  name: azure-vote-front
73	spec:
74	  type: LoadBalancer
75	  ports:
76	  - port: 80
77	  selector:
78		app: azure-vote-front

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

在 Cloud Shell 代码编辑器中保存更改

图 2.21:保存 azure-vote.yaml 文件

文件应该已保存。你可以通过以下命令来检查:

cat azure-vote.yaml

注意:

按下Tab按钮会在 Linux 中展开文件名。在前述情况下,如果你在输入az后按下Tab,它应该会展开为azure-vote.yaml

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

kubectl create -f azure-vote.yaml

你应该很快看到如图 2.22所示的输出,它会告诉你哪些资源已被创建:

kubectl create 命令的输出

图 2.22:kubectl create 命令的输出

你已经成功创建了演示应用程序。在接下来的部分,你将检查 Kubernetes 为该应用程序创建的所有不同对象,并连接到你的应用程序。

探索演示应用程序

在上一部分,你已经部署了一个演示应用程序。在这一部分,你将探索 Kubernetes 为该应用程序创建的不同对象并连接到它。

你可以通过输入以下命令来检查部署的进度:

kubectl get pods

如果你在创建应用程序后不久输入此命令,你可能会看到某个 Pod 仍处于ContainerCreating过程:

kubectl get pods 命令的输出

图 2.23:kubectl get pods 命令的输出

注意

输入kubectl可能会变得乏味。你可以使用alias命令来简化操作。你可以使用k代替kubectl作为别名,命令如下:alias k=kubectl。执行上述命令后,你可以直接使用k get pods。为了本书的教学目的,我们将继续使用完整的kubectl命令。

上箭头键并按Enter重复执行kubectl get pods命令,直到所有 Pod 的状态为Running。设置所有 Pod 需要一些时间,你也可以选择使用以下命令跟踪它们的状态:

kubectl get pods --watch

要停止跟踪 Pod 的状态(当它们都处于运行状态时),你可以按Ctrl + C

为了使你的应用能够公开访问,你还需要一个额外的信息。你需要知道负载均衡器的公共 IP 地址,这样你才能访问它。如果你记得在第一章,《容器与 Kubernetes 简介》中提到,Kubernetes 中的服务将创建一个 Azure 负载均衡器。这个负载均衡器会为你的应用获取一个公共 IP,从而使你能够公开访问它。

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

kubectl get service azure-vote-front --watch

一开始,外部 IP 可能显示为pending。等待公共 IP 出现后,再按Ctrl + C退出:

观察服务 IP 从待定状态变化为实际 IP 地址

图 2.24:观察服务 IP 从待定状态变化为实际 IP 地址

请注意外部 IP 地址并在浏览器中输入。你应该看到类似图 2.25的输出:

你刚刚启动的应用在浏览器中显示的输出

图 2.25:你刚刚启动的实际应用

点击“Cats”或“Dogs”,并观察计数增加。

要查看为你的应用在 Kubernetes 中创建的所有对象,你可以使用kubectl get all命令。这将显示类似图 2.26的输出:

通过执行 kubectl get all 命令查看所有对象

图 2.26:探索为你的应用创建的所有 Kubernetes 对象

如你所见,创建了许多对象:

  • Pods:你将看到两个 Pod,一个用于后端,一个用于前端。

  • 服务:你还会看到两个服务,一个用于后端,类型为ClusterIP,另一个用于前端,类型为 LoadBalancer。这些类型的含义将在第三章,《在 AKS 上进行应用部署》中探讨。

  • 部署:你还将看到两个部署。

  • 副本集:最后你将看到两个副本集。

你也可以通过 Azure 门户查看这些对象。例如,要查看两个部署,你可以点击 AKS 面板左侧导航菜单中的工作负载,然后你将看到集群中的所有部署,如图 2.27所示。此图显示了集群中的所有部署,包括系统部署。在列表底部,你可以看到你自己的部署。正如图中所示,你还可以通过顶部菜单查看其他对象,如 pod 和 ReplicaSet:

通过 Azure 门户查看我们集群中的所有部署

图 2.27:在 Azure 门户中查看应用程序的两个部署部分

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

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

通过运行 kubectl delete 命令删除所有对象

图 2.28:清理应用程序

在本节中,你已经通过 Cloud Shell 连接到你的 AKS 集群,成功启动并连接到演示应用程序,探索了通过 Cloud Shell 和 Azure 门户创建的对象,并最终清理了已创建的资源。

总结

完成本章后,你将能够访问并导航 Azure 门户,以执行所有部署 AKS 集群所需的功能。我们利用 Azure 的免费试用版,深入了解了 AKS 的各个方面。我们还启动了自己的 AKS 集群,并能够根据需要通过 Azure 门户自定义配置。

我们还使用了 Cloud Shell,而无需在计算机上安装任何东西。这对于接下来的所有章节非常重要,因为你将不仅仅是启动简单的应用程序。最后,我们启动了一个公开可访问的服务。这个应用程序的框架与我们将在后续章节中讨论的复杂应用程序相同。

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

第二部分:在 AKS 上部署

到目前为止,您已经学习了容器和 Kubernetes 的基础知识,并在 Azure 上设置了 Kubernetes 集群。在本节中,您将学习如何在该 Kubernetes 集群上部署应用程序。

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

本节包含以下章节:

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

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

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

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

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

本节的开始,我们将通过探讨在第三章,AKS 上的应用程序部署来进行。

第四章:3. 在 AKS 上部署应用程序

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

为了部署应用程序并对其进行更改,你将使用YAML文件。YAML 是 YAML Ain't Markup Language 的递归缩写。YAML 是一种用于创建配置文件以部署到 Kubernetes 的语言。虽然你可以使用 JSON 或 YAML 文件将应用程序部署到 Kubernetes,但 YAML 是最常用的语言。YAML 之所以受欢迎,是因为它比 JSON 或 XML 更易于人类阅读。你将在本章和整本书中看到多个 YAML 文件的示例。

在部署示例的留言簿应用程序时,你将看到 Kubernetes 概念的实际应用。你将看到部署是如何与ReplicaSet关联的,且ReplicaSet又是如何与已部署的pods关联的。部署是 Kubernetes 中的一个对象,用于定义应用程序的期望状态。部署将创建一个 ReplicaSet。ReplicaSet是 Kubernetes 中的一个对象,确保始终有一定数量的pods可用。因此,ReplicaSet 将创建一个或多个 pods。Pod 是 Kubernetes 中的一个对象,它是一个或多个容器的集合。让我们再来看一下部署、ReplicaSets 和 pods 之间的关系:

展示部署创建一个 replicaset,而 replicaset 又创建多个 pods 的关系

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

在部署示例应用程序时,你将使用service对象连接到应用程序。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 集群是有状态的,因为它存储所有留言簿条目。

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

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

介绍该应用程序

该应用程序存储和显示留言簿条目。你可以用它记录所有访问你酒店或餐馆的人的意见。

图 3.2 显示了该应用程序的高层概述。该应用程序使用 PHP 作为前端。前端将通过多个副本进行部署。该应用程序使用 Redis 进行数据存储。Redis 是一个内存中的键值数据库。Redis 最常用作缓存。

多层次留言簿应用程序的架构

图 3.2:留言簿应用程序的高层概述

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

部署 Redis 主节点

在本节中,你将部署 Redis 主节点。你将学习此部署所需的 YAML 语法。在下一节中,你将对这个 YAML 进行更改。在进行更改之前,我们先开始部署 Redis 主节点。

执行以下步骤以完成任务:

  1. 打开你友好的 Azure 云 Shell,如 图 3.3 所示:从 Azure 门户打开云 Shell

    图 3.3:打开云 Shell

  2. 如果你还没有克隆本书的 GitHub 仓库,请现在使用以下命令进行克隆:

    git clone https: //github.com/PacktPublishing/Hands-on-Kubernetes-on-Azure-Third-Edition/
    
  3. 使用以下命令切换到第三章的目录:

    cd Hands-On-Kubernetes-on-Azure/Chapter03/
    
  4. 输入以下命令以部署主节点:

    kubectl apply -f redis-master-deployment.yaml
    

    应用程序下载并启动需要一些时间。等待时,让我们来理解一下你刚刚输入并执行的命令。我们先从探索用于该命令的 YAML 文件内容开始(行号用于解释代码片段中的关键元素):

    1   apiVersion: apps/v1
    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          resources:
    25            requests:
    26              cpu: 100m
    27              memory: 100Mi
    28            limits:
    29              cpu: 250m
    30              memory: 1024Mi
    31          ports:
    32          - containerPort: 6379
    

    让我们逐行深入分析代码,理解提供的参数:

    • deployment 被命名为 redis-master

    • app: redis, role: master, and tier: backend)。前面的标签与第14-19行提供的标签完全匹配。

    • master。在多容器 Pod 的情况下,每个容器都需要一个唯一的名称。

    • redis 镜像标记为 e2e(已通过端到端[e2e]测试的最新 Redis 镜像)。

    • cpu/memory 为容器请求的资源。在 Kubernetes 中,资源请求是资源的保留,其他 Pod 无法使用这些资源。如果这些资源在集群中不可用,Pod 将无法启动。在这种情况下,请求的是 0.1 CPU,等于 100m,也通常称为 100 毫核。请求的内存是 100Mi,即 104,857,600 字节,约等于 105 MB。CPU 和内存限制以类似方式设置。限制是容器可以使用的最大资源。如果 Pod 达到 CPU 限制,它将被限制使用,而如果达到了内存限制,它将被重启。在 Kubernetes 中,设置请求和限制是最佳实践。更多信息,请参见 kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

    • 6379

如你所见,部署的 YAML 定义包含多个 Kubernetes 将用于部署和配置应用程序的设置和参数。

注意

Kubernetes 的 YAML 定义类似于给 Docker 运行特定容器镜像时提供的参数。如果你需要手动运行该命令,你将以以下方式定义此示例:

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

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中的输出。在你的情况中,Pod 和 ReplicaSet 的名称可能会在名称末尾包含不同的 ID。如果你没有看到 Pod、部署和 ReplicaSet,请按照上一节第 4 步中的说明再次运行代码。

由你的部署创建的对象列表

图 3.4:由你的部署创建的对象

你可以看到你创建了一个名为 redis-master 的部署。它控制一个名为 redis-master-f46ff57fd 的 ReplicaSet。进一步检查后,你还会发现该 ReplicaSet 控制着一个 pod,redis-master-f46ff57fd-b8cjp图 3.1 以图示方式展示了这种关系。

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

kubectl describe deployment/redis-master

这将生成如下输出:

使用 kubectl describe 命令获取部署的详细信息

图 3.5:部署的描述

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

在下一部分,你将了解一个新概念,叫做 ConfigMap,并重建 Redis 主节点。因此,在继续之前,请清理当前版本,你可以通过运行以下命令来完成:

kubectl delete deployment/redis-master

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

deployment.apps "redis-master" deleted

在本节中,你检查了所创建的 Redis 主节点部署。你看到了部署如何与 ReplicaSet 关联,ReplicaSet 又如何与 pods 关联。在接下来的部分,你将使用通过 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
    
  2. 现在你可以使用以下代码创建 ConfigMap:

    kubectl create configmap \
      example-redis-config --from-file=redis-config
    

    你应该获得如下输出:

    configmap/example-redis-config created
    
  3. 你可以使用相同的命令描述此 ConfigMap:

    kubectl describe configmap/example-redis-config
    

    输出将如 图 3.6 所示:

使用 kubectl describe 命令获取 ConfigMap 的描述

图 3.6:ConfigMap 描述

在这个例子中,你通过引用磁盘上的文件来创建了 ConfigMap。另一种部署 ConfigMap 的方式是通过从 YAML 文件中创建它们。让我们在下一节中看看如何做到这一点。

从 YAML 文件创建 ConfigMap

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

  1. 首先,删除先前创建的 ConfigMap:

    kubectl delete configmap/example-redis-config
    
  2. 将以下内容复制并粘贴到名为 example-redis-config.yaml 的文件中,然后保存:

    1  apiVersion: v1
    2  data:
    3    redis-config: |-
    4      maxmemory 2mb
    5      maxmemory-policy allkeys-lru
    6  kind: ConfigMap
    7  metadata:
    8    name: example-redis-config
    
  3. 现在你可以通过以下命令创建你的 ConfigMap:

    kubectl create -f example-redis-config.yaml
    

    你应该会得到如下输出:

    configmap/example-redis-config created
    
  4. 接下来,运行以下命令:

    kubectl describe configmap/example-redis-config
    

    此命令返回与之前相同的输出,如 图 3.6 所示。

如你所见,通过使用 YAML 文件,你成功创建了相同的 ConfigMap。

注意

kubectl get 具有有用的 -o 选项,可以用来获取对象的输出,无论是 YAML 还是 JSON 格式。这在你手动更改系统并希望查看结果对象的 YAML 格式时非常有用。你可以使用以下命令获取当前 ConfigMap 的 YAML 输出:

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

现在你已经定义了 ConfigMap,接下来我们来使用它。

使用 ConfigMap 读取配置数据

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

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

    1  apiVersion: apps/v1
    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
    

    注意

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

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

    • redis-server 指向特定的配置文件。

    • docker run -e "MASTER=true". --name master -p 6379:6379 -m 100M -c 100m -d Kubernetes /redis:v1。这将环境变量 MASTER 设置为 true。你的应用程序可以读取环境变量设置来进行配置。

    • config(该卷在第 39-45 行定义)位于正在运行的容器中的 /redis-master 路径。它将隐藏原容器中 /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

    • config。这个名称将在该 pod 的上下文中使用。

    • example-redis-config ConfigMap。该 ConfigMap 应该已经存在于系统中。你已经定义了它,所以一切正常。

    • redis-config 键(两行 maxmemory 设置)作为 redis.conf 文件。

通过将 ConfigMap 作为卷添加并挂载卷,你可以加载动态配置。

  1. 让我们创建这个更新后的部署:

    kubectl create -f redis-master-deployment_Modified.yaml
    

    这应该输出以下内容:

    deployment.apps/redis-master created
    
  2. 现在让我们确认配置是否成功应用。首先,获取 Pod 的名称:

    kubectl get pods
    

    这应该返回类似于图 3.7的输出:

    使用 kubectl get pods 命令获取 Redis-master Pod 的详细信息

    图 3.7:Pod 详情

  3. 然后 exec 进入 Pod 并验证设置是否已应用:

    kubectl exec -it redis-master-<pod-id> -- redis-cli
    

    这会打开一个与正在运行的 Pod 连接的 redis-cli 会话。现在你可以获取 maxmemory 配置:

    CONFIG GET maxmemory
    

    然后你可以获取 maxmemory-policy 配置:

    CONFIG GET maxmemory-policy
    

    这应该给你类似于图 3.8的输出:

    验证 maxmemory 和 maxmemory-policy 自定义配置

    图 3.8:在 Pod 中验证 Redis 配置

  4. 要离开 Redis shell,请键入 exit 命令。

总结一下,你刚刚完成了配置云原生应用程序的重要步骤,即为应用程序提供动态配置数据。你还会注意到,应用程序必须配置为动态读取配置。在你设置应用程序配置后,你访问了一个运行中的容器来验证运行配置。你将在本书中频繁使用这种方法来验证正在运行的应用程序的功能。

注意

使用 kubectl exec 命令连接到正在运行的容器对于故障排除和诊断非常有用。由于容器是短暂的,绝不要通过连接容器来进行额外的配置或安装。这些应该是容器镜像的一部分,或者是通过 Kubernetes 提供的配置(就像你刚才做的那样)。

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

完整部署示例访客簿应用程序

通过绕道了解使用 ConfigMap 动态配置应用程序后,你将返回到部署其余访客簿应用程序的部分。你将再次遇到部署、ReplicaSets 和 Pod 的概念。除此之外,你还将接触到另一个关键概念——服务。

要启动完整部署,我们将创建一个服务来暴露 Redis 主服务。

暴露 Redis 主服务

在普通 Docker 中暴露端口时,暴露的端口仅限于运行该容器的主机。使用 Kubernetes 网络时,集群中的不同 Pod 之间有网络连接。然而,Pod 本身是短暂的,意味着它们可以被关闭、重启,甚至移到其他主机而不保持其 IP 地址。如果你直接连接到 Pod 的 IP 地址,如果该 Pod 被移到新主机,可能会失去连接。

Kubernetes 提供了 service 对象,用于解决这个问题。通过使用标签匹配选择器,它将流量发送到正确的 pods。如果有多个 pods 为一个服务提供流量,它还会进行负载均衡。在这种情况下,主节点只有一个 pod,所以它确保流量无论 pod 运行在哪个节点上,都会被定向到该 pod。要创建服务,请运行以下命令:

kubectl apply -f redis-master-service.yaml

redis-master-service.yaml 文件包含以下内容:

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

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

  • redis-master,它具有与我们的 redis-master 服务器 pod 相同的标签。

  • 6379 端口转发到与第 13 行到第 16 行之间定义的选择器匹配的 pods 的 6379 端口。

  • app: redis, role: master and tier: backend)预计处理 6379 端口流量。如果回顾前面的示例,这些正是我们应用于该部署的标签。

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

kubectl get service

这将为您提供一个输出,如 图 3.9 所示:

使用 kubectl get service 命令获取 Redis-master 服务的属性

图 3.9:创建的服务的属性

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

注意

现在您正在创建一个 ClusterIP 类型的服务。后续章节将介绍其他类型的服务。

服务还会引入一个 <service-name>.<namespace>.svc.cluster.local;在本例中,它将是 redis-master.default.svc.cluster.local。为了查看这个功能,我们将在 redis-master pod 上进行域名解析。默认镜像没有安装 nslookup,所以我们通过运行 ping 命令来绕过这一点。如果该流量没有返回,请不要担心;这是因为您没有在服务中暴露 ping,只暴露了 redis 端口。然而,这个命令对于查看完整的 DNS 名称以及域名解析的工作非常有用。让我们来看一下:

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

这应该会输出结果的名称解析,显示 exit 命令,如 图 3.10 所示:

使用 ping 命令查看服务的 FQDN

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

在本节中,您通过服务暴露了 Redis 主节点。这确保了即使一个 pod 移动到不同的主机,也可以通过服务的 IP 地址访问。在下一节中,您将部署 Redis 副本,它们有助于处理更多的读取流量。

部署 Redis 副本

在云端运行单一后端并不推荐。你可以将 Redis 配置为主从(leader-follower)模式。这意味着你可以有一个主节点处理写操作流量,多个副本处理读操作流量。这有助于处理增加的读流量和提高可用性。

让我们来配置这个:

  1. 通过运行以下命令创建部署:

    kubectl apply -f redis-replica-deployment.yaml
    
  2. 让我们检查一下当前已创建的所有资源:

    kubectl get all
    

    输出将如图 3.11所示:

    使用 kubectl get all 命令显示所有已创建的对象

    图 3.11:部署 Redis 副本会创建多个新对象

  3. 根据之前的输出,你可以看到你创建了两个 redis-replica Pods 的副本。可以通过查看 redis-replica-deployment.yaml 文件来确认这一点:

    1   apiVersion: apps/v1
    2   kind: Deployment
    3   metadata:
    4     name: redis-replica
    5     labels:
    6       app: redis
    7   spec:
    8     selector:
    9       matchLabels:
    10       app: redis
    11       role: replica
    12       tier: backend
    13   replicas: 2
    14   template:
    15     metadata:
    16       labels:
    17         app: redis
    18         role: replica
    19         tier: backend
    20     spec:
    21       containers:
    22       - name: replica
    23         image: gcr.io/google-samples/gb-redis-follower:v1 24         resources:
    25           requests:
    26             cpu: 100m
    27             memory: 100Mi
    28         env:
    29         - name: GET_HOSTS_FROM
    30           value: dns
    31         ports:
    32         - containerPort: 6379
    

    除了以下几点,其余部分都是相同的:

    • GET_HOSTS_FROM 设置为 dns。这是一个设置,指定 Redis 应该使用 DNS 获取主节点的主机名。

    如你所见,这与之前创建的 Redis 主节点类似。

  4. 与主服务一样,你需要通过运行以下命令暴露副本服务:

    kubectl apply -f redis-replica-service.yaml
    

    这个服务和 redis-master 服务的唯一不同之处在于,它将流量代理到具有 role:replica 标签的 Pods。

  5. 通过运行以下命令检查 redis-replica 服务:

    kubectl get service
    

    这将给出图 3.12中所示的输出:

Redis 主节点与 Redis 副本配置详细信息

图 3.12:Redis-master 和 redis-replica 服务

现在你已经拥有一个运行中的 Redis 集群,其中包含一个主节点和两个副本。在接下来的部分,你将部署并暴露前端。

部署并暴露前端

到目前为止,你主要集中在 Redis 后端。现在你已经准备好部署前端了。这将为你的应用程序添加一个可以交互的图形网页。

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

kubectl apply -f frontend-deployment.yaml

要验证部署,请运行以下命令:

kubectl get pods

这将显示在图 3.13中展示的输出:

前端部署详细信息

图 3.13:验证前端部署

你会注意到,这个部署指定了3个副本。这个部署有通常的组成部分,只有少许改动,如下代码所示:

1  apiVersion: apps/v1
2   kind: Deployment
3   metadata:
4     name: frontend
5     labels:
6       app: guestbook
7   spec:
8     selector:
9       matchLabels:
10        app: guestbook
11        tier: frontend
12    replicas: 3
13    template:
14      metadata:
15        labels:
16          app: guestbook
17          tier: frontend
18      spec:
19        containers:
20        - name: php-redis
21          image: gcr.io/google-samples/gb-frontend:v4
22          resources:
23            requests:
24              cpu: 100m
25              memory: 100Mi
26          env:
27          - name: GET_HOSTS_FROM
28            value: env
29          - name: REDIS_SLAVE_SERVICE_HOST
30            value: redis-replica
31          ports:
32          - containerPort: 80

让我们来看一下这些变化:

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

  • app: guestbooktier: frontend

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

你现在已经创建了前端部署。接下来,你需要将其暴露为服务。

暴露前端服务

定义 Kubernetes 服务有多种方式。我们创建的两个 Redis 服务都是 ClusterIP 类型。这意味着它们在一个仅能从集群内部访问的 IP 上暴露,如图 3.14所示:

Kubernetes ClusterIP 类型服务

图 3.14:Kubernetes ClusterIP 类型服务

另一种服务类型是NodePort类型。NodePort类型的服务可以通过连接到节点的 IP 和指定端口从集群外部访问。此服务在每个节点上通过静态端口进行暴露,如图 3.15所示:

Kubernetes 类型为 NodePort 的服务

图 3.15:Kubernetes 类型为 NodePort 的服务

一个最终的类型——在本示例中使用的类型——是LoadBalancer类型。它将创建一个Azure 负载均衡器,该负载均衡器会获取一个公共 IP,你可以用来连接,如图 3.16所示:

Kubernetes 类型为 LoadBalancer 的服务

图 3.16:Kubernetes 类型为 LoadBalancer 的服务

以下代码将帮助你理解前端服务是如何暴露的:

1   apiVersion: v1
2   kind: Service
3   metadata:
4     name: frontend
5     labels:
6       app: guestbook
7       tier: frontend
8   spec:
9     type: LoadBalancer # line uncommented
10    ports:
11    - port: 80
12    selector:
13      app: guestbook
14      tier: frontend

这个定义与之前创建的服务类似,唯一的区别是,在第 9 行你定义了type: Load Balancer。这将创建这种类型的服务,从而使 AKS 在 Azure 负载均衡器中添加规则。

现在你已经看到了前端服务是如何暴露的,接下来让我们通过以下步骤使留言簿应用程序准备就绪:

  1. 要创建服务,请运行以下命令:

    kubectl create -f frontend-service.yaml
    

    当你第一次运行时,这一步需要一些时间来执行。在后台,Azure 必须执行几个操作以确保无缝连接。它需要创建一个 Azure 负载均衡器和一个公共 IP,并设置端口转发规则,将80端口的流量转发到集群的内部端口。

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

    kubectl get service -w
    

    这应该显示如图 3.17所示的输出:

    获取前端部署的外部 IP 值

    图 3.17:外部 IP 值

  3. 在 Azure 门户中,如果你点击“所有资源”并过滤负载均衡器,你将看到一个 Kubernetes 负载均衡器。点击它会显示类似图 3.18的内容。高亮部分显示了一个负载均衡规则,接受80端口的流量,你有两个公共 IP 地址:

Azure 负载均衡器显示接受 80 端口流量的负载均衡规则

图 3.18:Azure 门户中的 Kubernetes 负载均衡器

如果你点击这两个公共 IP 地址,你会看到这两个 IP 地址都与集群相关联。其中一个将是你实际前端服务的 IP 地址,另一个则由 AKS 用于发起出站连接。

注意

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

基础型负载均衡器背后的虚拟机可以在没有任何特定配置的情况下进行出站连接。标准型负载均衡器(这是 AKS 当前的默认设置)背后的虚拟机需要在负载均衡器上设置出站规则才能进行出站连接。这就是为什么你会看到第二个 IP 地址被配置的原因。

你终于准备好看到你的留言簿应用程序运行了!

留言簿应用程序正在运行

在你喜欢的浏览器中输入服务的公共 IP 地址,你应该能看到图 3.19所示的输出:

使用公共 IP 地址查看 Guestbook 应用的运行情况

图 3.19:Guestbook 应用的运行情况

继续记录你的留言,它们将被保存。再打开一个浏览器,输入相同的 IP 地址,你将看到你输入的所有留言。

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

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

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

在前面的章节中,你已部署了 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 文件,这些参数可以动态设置。你可以通过 values 文件或作为命令行变量在部署 chart 时设置这些参数。

最后,使用 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 在你的集群上安装 WordPress 开始。在本节中,你还将学习 Kubernetes 中的持久存储。

使用 Helm 安装 WordPress

如介绍中所提到,Helm 拥有丰富的预编写 Helm Charts 库。要访问此库,你需要将仓库添加到你的 Helm 客户端中:

  1. 使用以下命令添加包含稳定 Helm Charts 的仓库:

    helm repo add bitnami \
      https://charts.bitnami.com/bitnami
    
  2. 要安装 WordPress,运行以下命令:

    helm install handsonakswp bitnami/wordpress
    

    这个操作将导致 Helm 安装github.com/bitnami/charts/tree/master/bitnami/wordpress中详细说明的 chart。

Helm 安装需要一些时间,网站加载时我们先来看一个关键概念:PersistentVolumeClaims。讲解完这个概念后,我们再回头看看已创建的网站。

PersistentVolumeClaims

一个典型的过程需要计算、内存、网络和存储。在 Guestbook 示例中,我们看到 Kubernetes 如何帮助我们抽象计算、内存和网络。这些相同的 YAML 文件适用于所有云服务商,包括云服务商特定的公共负载均衡器设置。WordPress 示例展示了最后一块拼图——即存储——如何从底层云服务商中抽象出来。

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

与无状态应用程序(例如我们的前端)不同,MariaDB 需要仔细处理存储。为了让 Kubernetes 处理有状态工作负载,它有一个特定的对象,称为 <pod-name>-#,其中 # 从第一个 pod 的 0 开始,第二个 pod 为 1

运行以下命令,你可以看到 MariaDB 附加了一个可预测的数字,而 WordPress 部署则在末尾附加了一个随机数字:

kubectl get pods

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

使用 StatefulSet 命名 Pod

图 3.20:附加到 MariaDB 和 WordPress Pod 的数字

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

另一个区别是 Pod 删除的处理方式。当删除一个部署 Pod 时,Kubernetes 会在任何地方重新启动它,而当删除一个 StatefulSet Pod 时,Kubernetes 只会在原本运行该 Pod 的节点上重新启动它。只有当节点从 Kubernetes 集群中移除时,Pod 才会被迁移到其他节点。

通常,你需要将存储附加到 StatefulSet。为此,StatefulSet 需要一个 PersistentVolume (PV)。这个存储可以由多种机制支持(包括块存储,如 Azure Blob、EBS 和 iSCSI,以及网络文件系统,如 AFS、NFS 和 GlusterFS)。StatefulSets 需要一个预先配置的存储卷或由 PersistentVolumeClaim (PVC) 动态管理的存储卷。PVC 允许用户动态请求存储,这将导致 PV 的创建。

请参考kubernetes.io/docs/concepts/storage/persistent-volumes/以获取更详细的信息。

在这个 WordPress 示例中,您正在使用一个 PVC。PVC 提供了对底层存储机制的抽象。让我们通过运行以下命令来查看 MariaDB Helm Chart 的操作:

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

在前面的命令中,您获得了创建的 StatefulSet 的 YAML 定义,并将其存储在一个名为 mariadbss.yaml 的文件中。让我们来看看该 YAML 文件中最相关的部分。代码已被截断,仅显示最相关的部分:

1   apiVersion: v1
2   items:
3   - apiVersion: apps/v1
4     kind: StatefulSet
...
285           volumeMounts:
286           - mountPath: /bitnami/mariadb
287             name: data
...           
306 volumeClaimTemplates:
307 - apiVersion: v1
308   kind: PersistentVolumeClaim
309   metadata:
310     creationTimestamp: null
311     labels:
312       app.kubernetes.io/component: primary
313       app.kubernetes.io/instance: handsonakswp
314       app.kubernetes.io/name: mariadb
315     name: data
316   spec:
317     accessModes:
318     - ReadWriteOnce
319     resources:
320       requests:
321         storage: 8Gi
322     volumeMode: Filesystem
...

前面代码的大部分元素在部署时已经涵盖。接下来的几点我们将重点突出关键的区别,单独查看 PVC:

注意

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

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

  • StatefulSet 声明。

  • data 并将其挂载到 /bitnami/mariadb 路径下。

  • data,在第 285 行重复使用。

  • ReadWriteOnce,这将创建块存储,在 Azure 上即为磁盘。还有其他访问模式,分别是 ReadOnlyManyReadWriteMany。顾名思义,ReadWriteOnce 卷只能附加到单个 pod 上,而 ReadOnlyManyReadWriteMany 卷可以同时附加到多个 pod 上。这两种类型需要不同的底层存储机制,如 Azure 文件或 Azure Blob。

  • 第 321 行:此行定义了磁盘的大小。

根据前面的信息,Kubernetes 动态请求并将一个 8 GiB 的卷绑定到这个 pod。此时,使用的是由 Azure 磁盘提供支持的默认动态存储提供者。动态存储提供者是在创建集群时由 Azure 设置的。要查看集群中可用的存储类,您可以运行以下命令:

kubectl get storageclass

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

集群中可用的存储类列表

图 3.21:集群中不同的存储类

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

kubectl get pvc

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

创建的集群中 PVC 的列表

图 3.22:集群中不同的 PVC

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

获取与 PVC 关联的磁盘

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

PVC 的概念抽象了云提供商存储的具体实现。这允许相同的 Helm 模板在 Azure、AWS 或 GCP 上都能正常工作。在 AWS 上,它将由弹性块存储EBS)支持,而在 GCP 上,它将由 Persistent Disk 支持。

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

本节介绍了在 Kubernetes 中使用PersistentVolumeClaimPVC)的存储概念。您了解了它们是如何通过 WordPress Helm 部署创建的,以及 Kubernetes 如何创建 Azure 磁盘来支持 MariaDB 使用的 PVC。在下一节中,您将更详细地探索 Kubernetes 中的 WordPress 应用程序。

检查 WordPress 部署

在我们分析了 PVC 之后,让我们回到 Helm 部署。您可以使用以下命令检查部署状态:

helm ls

这应该返回如图 3.24所示的输出:

检查 WordPress 应用程序在 Helm 中的部署状态

图 3.24:WordPress 应用程序部署状态

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

helm status handsonakswp

这将返回如图 3.25所示的输出:

使用 helm status 命令获取更多 WordPress 部署的详细信息

图 3.25:获取更多部署详情

这显示了您的 chart 已成功部署。它还提供了如何连接到站点的更多信息。您目前不会使用这些步骤;您将在第五章,处理 AKS 中的常见故障中重新访问这些步骤,在我们讨论修复存储挂载问题的部分。现在,让我们看看 Helm 为您创建的所有内容:

kubectl get all

这将生成一个类似于图 3.26的输出:

Helm 创建的对象列表

图 3.26:由 Helm 创建的对象列表

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

然后,您可以连接到您的外部 IP 并访问您的 WordPress 站点。图 3.27是结果输出:

使用外部 IP 连接到 WordPress 站点

图 3.27:通过外部 IP 连接显示的 WordPress 站点

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

helm delete handsonakswp

按设计,PVC 不会被删除。这确保了持久数据得以保存。由于您没有持久数据,您可以安全地删除 PVC:

kubectl delete pvc --all

注意

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

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

概要

在本章中,您部署了两个应用程序。您从部署 guestbook 应用程序开始。在该部署过程中,探讨了 Pods、ReplicaSets 和 Deployments 的细节。您还使用了 ConfigMaps 进行动态配置。最后,您了解了如何使用服务来将流量路由到已部署的应用程序。

你部署的第二个应用程序是 WordPress 应用程序。你通过 Helm 包管理器部署了它。在这次部署中,使用了 PVC,并且你探索了它们如何在系统中使用,以及如何与 Azure 上的磁盘关联。

第四章,构建可扩展应用程序中,你将探讨如何扩展应用程序及其集群。你将首先学习应用程序的手动和自动扩展,之后,你将学习集群本身的手动和自动扩展。最后,将解释应用程序在 Kubernetes 上的不同更新方式。

第五章:4. 构建可扩展的应用程序

在高效运行应用程序时,能够扩展和升级应用程序至关重要。扩展可以让你的应用程序处理额外的负载。而在升级过程中,扩展是保持应用程序最新并引入新功能的必要手段。

按需扩展是使用云原生应用程序的关键好处之一。它还可以帮助优化应用程序的资源。如果前端组件遇到高负载,你可以单独扩展前端,同时保持后端实例的数量不变。你可以根据工作负载和高峰时段调整所需的虚拟机VM)数量。本章将详细介绍应用程序及其基础设施的扩展维度。

在本章中,你将学习如何扩展在第三章中介绍的示例访客簿应用程序,即在 AKS 上部署应用程序。你将首先使用手动命令扩展该应用程序,然后你将学习如何使用kubectl进行自动扩展,kubectl是管理运行在Azure Kubernetes ServiceAKS)上的应用程序的重要工具。扩展应用程序后,你还将扩展集群。你将首先手动扩展集群,然后使用集群自动扩展器来自动扩展集群。此外,你还将简要了解如何升级在 AKS 上运行的应用程序。

本章将涵盖以下主题:

  • 扩展你的应用程序

  • 扩展你的集群

  • 升级你的应用程序

本章的开始部分将讨论如何在 AKS 上扩展应用程序的不同维度。

扩展你的应用程序

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

通过向部署中添加新的 Pod,也称为扩展,你可以为已部署的应用程序增加计算能力。你可以手动扩展应用程序,或者通过 HPA 让 Kubernetes 自动处理这一过程。HPA 可以监控 CPU 等指标,以判断是否需要为你的部署添加 Pod。

AKS 中的第二个扩展维度是集群中节点的数量。集群中的节点数量定义了所有在该集群上运行的应用程序可以使用的 CPU 和内存资源。你可以通过更改节点数量手动扩展集群,或者使用集群自动扩展器自动扩展集群。集群自动扩展器会监视集群中的 Pod,如果由于资源限制导致 Pod 无法调度,它会向集群添加节点,确保你的应用程序能够运行。

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

手动扩展您的应用程序

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

在上一章中,我们在 Cloud Shell 中克隆了示例文件。如果您当时没有执行此操作,我们建议现在进行操作:

git clone https://github.com/PacktPublishing/Hands-On-Kubernetes-on-Azure-third-edition

本章中,请导航到 Chapter04 目录:

cd Chapter04

  1. 通过在 Azure 命令行中运行 kubectl create 命令来设置留言簿:

    kubectl create -f guestbook-all-in-one.yaml
    
  2. 在输入前面的命令后,您应该会在命令行输出中看到类似图 4.1所示的内容:通过运行 kubectl create 命令设置留言簿应用程序

    图 4.1:启动留言簿应用程序

  3. 目前,所有服务都不可公开访问。我们可以通过运行以下命令来验证这一点:

    kubectl get service
    
  4. 图 4.2所示,所有服务都没有外部 IP:验证确保没有任何服务是公开可访问的

    图 4.2:确认没有服务具有公共 IP 的输出

  5. 为了测试应用程序,您需要将其公开。为此,我们将介绍一个新命令,允许您在不修改文件系统中文件的情况下编辑 Kubernetes 中的服务。要开始编辑,请执行以下命令:

    kubectl edit service frontend
    
  6. 这将打开一个 vi 环境。使用下箭头键导航到显示 type: ClusterIP 的行,并将其更改为 type: LoadBalancer,如图 4.3所示。要进行此更改,按下 I 键,将 type 改为 LoadBalancer,然后按 Esc 键,输入 :wq!,并按 Enter 保存更改:将类型从 ClusterIP 更改为 LoadBalancer

    图 4.3:将该行更改为 type: LoadBalancer

  7. 一旦更改保存完毕,您可以观察服务对象,直到公共 IP 可用。为此,请输入以下命令:

    kubectl get service -w
    
  8. 更新的 IP 地址将需要几分钟时间才能显示。一旦看到正确的公共 IP,您可以通过按 Ctrl + C 退出 watch 命令:显示前端服务获取公共 IP 的输出

    图 4.4:显示前端服务获取公共 IP 的输出

  9. 将前面输出的 IP 地址输入到浏览器导航栏中,如下所示:http://<EXTERNAL-IP>/。结果如图 4.5所示:

在浏览器中输入 IP 地址查看留言簿示例

图 4.5:浏览到留言簿应用程序

熟悉的留言板示例应该可见。这表明你已成功公开访问了留言板。

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

扩展留言板前端组件

Kubernetes 让我们能够动态地扩展应用的每个组件。在本节中,我们将展示如何扩展留言板应用的前端。目前,前端部署已部署了三个副本。你可以通过以下命令确认:

kubectl get pods

这应该会返回如图 4.6所示的输出:

确认前端部署中有三个副本的输出

图 4.6:确认前端部署中的三个副本

要扩展前端部署,你可以执行以下命令:

kubectl scale deployment/frontend --replicas=6

这将导致 Kubernetes 为部署添加额外的 pod。你可以设置所需的副本数量,Kubernetes 会处理剩余的工作。你甚至可以将副本数扩展到零(这是当应用程序不支持动态配置重新加载时,重新加载配置的一种技巧)。要验证整体扩展是否正确工作,你可以使用以下命令:

kubectl get pods

这应该会给你与图 4.7中相同的输出:

扩展后前端部署中运行的 6 个 pod 的输出

图 4.7:扩展后在留言板应用中运行的不同 pod

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

kubectl get pods -o wide

这将生成以下输出:

显示 pod 运行节点的输出

图 4.8:显示 pod 运行在哪些节点上

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

使用 HPA

手动扩展在你操作集群时非常有用。例如,如果你知道负载将增加,你可以手动扩展应用程序。然而,在大多数情况下,你会希望应用程序能够进行某种形式的自动扩展。在 Kubernetes 中,你可以使用一个叫做水平 Pod 自动扩展器HPA)的对象来配置部署的自动扩展。

HPA 定期监控 Kubernetes 度量标准,并根据您定义的规则自动扩展部署。例如,您可以配置 HPA,在应用程序的 CPU 利用率超过 50%时,自动向部署中添加更多 Pods。

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

  1. 要开始配置,首先让我们手动将部署规模缩小到一个实例:

    kubectl scale deployment/frontend --replicas=1
    
  2. 接下来,我们将创建一个 HPA。通过在 Cloud Shell 中输入code hpa.yaml来打开代码编辑器,并输入以下代码:

    1   apiVersion: autoscaling/v1
    2   kind: HorizontalPodAutoscaler
    3   metadata:
    4     name: frontend-scaler
    5   spec:
    6     scaleTargetRef:
    7       apiVersion: apps/v1
    8       kind: Deployment
    9       name: frontend
    10    minReplicas: 1
    11    maxReplicas: 10
    12    targetCPUUtilizationPercentage: 50
    

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

    • HorizontalPodAutoscaler

    • 第 6-9 行:这些行定义了我们希望自动扩展的部署。

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

    • 第 12 行:在这里,我们定义了部署的目标 CPU 利用率百分比。

  3. 保存此文件,并使用以下命令创建 HPA:

    kubectl create -f hpa.yaml
    

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

    kubectl get hpa
    

    这最初将输出类似于图 4.9所示的内容:

    输出显示目标为未知,这表明 HPA 尚未准备好

    图 4.9:目标未知表示 HPA 尚未准备好

    HPA 读取度量标准需要几秒钟的时间。等待 HPA 的返回,输出应类似于图 4.10所示:

    输出显示目标有一个百分比,表示 HPA 已准备好

    图 4.10:当目标显示百分比时,HPA 已准备好

  4. 您现在需要做两件事:首先,您将观察 Pods,以查看是否创建了新 Pods。然后,您将创建一个新的 shell 并为系统生成一些负载。让我们从第一个任务开始——观察我们的 Pods:

    kubectl get pods -w
    

    这将持续监控创建或终止的 Pods。

    现在,让我们在新的 shell 中创建一些负载。在 Cloud Shell 中,点击打开新会话图标以打开一个新的 shell:

    点击打开新会话图标以打开新的 Cloud Shell 会话

    图 4.11:使用此按钮打开新的 Cloud Shell 会话

    这将打开浏览器中的新标签页,并在 Cloud Shell 中启动一个新的会话。您将从该标签页为应用程序生成负载。

  5. 接下来,您将使用一个名为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 开始扩展部署。触发扩展操作需要几分钟时间,但在某个时刻,您应该看到多个 Pods 被创建以处理额外的负载,如图 4.12所示:

    输出显示 HPA 正在创建新 Pods 以处理额外的负载

    图 4.12:新 Pod 由 HPA 启动

    此时,您可以通过按Ctrl + C终止hey程序。

  6. 让我们通过运行以下命令来更仔细地查看 HPA 做了什么:

    kubectl describe hpa
    

    我们可以在describe操作中看到一些有趣的点,如图 4.13所示:

    运行 kubectl describe hpa 命令以获取 HPA 的详细视图

    图 4.13:HPA 的详细视图

    图 4.13中的注释解释如下:

    • 这显示了当前的 CPU 利用率(384%)与期望值(50%)的对比。当前的 CPU 利用率在您的环境中可能会有所不同。

    • 这表明当前的期望副本数高于您配置的实际最大副本数。这确保了单个部署不会消耗集群中的所有资源。

    • 这显示了 HPA 所采取的扩展动作。它首先将副本数扩展到 4,然后到 8,最后扩展到 10 个 Pod。

  7. 如果等待几分钟,HPA 应该会开始缩减。您可以使用以下命令跟踪这个缩减操作:

    kubectl get hpa -w
    

    这将跟踪 HPA 并显示部署逐渐缩减的过程,如图 4.14所示:

    使用 kubectl get hpa -w 命令跟踪 HPA 缩减

    图 4.14:观看 HPA 缩减

  8. 在进入下一节之前,让我们清理一下本节中创建的资源:

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

在本节中,您首先手动然后自动扩展了应用程序。然而,支撑该应用程序的基础设施是静态的;您在一个两节点的集群上运行它。在许多情况下,您可能还会遇到集群资源不足的情况。在下一节中,您将处理这个问题,并学习如何自己扩展 AKS 集群。

扩展集群

在上一节中,您处理了在集群上运行的应用程序的扩展问题。在本节中,您将学习如何扩展您运行的实际集群。首先,您将手动将集群缩放到一个节点。然后,您将配置集群自动扩展器。集群自动扩展器将监控您的集群,当集群中有无法调度的 Pod 时,它会进行扩展。

手动扩展集群

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

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

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

  1. 打开 Azure 门户并进入您的集群。进入后,转到 Node pools,然后点击 Node count 下方的数字,如 图 4.15 所示:通过 Azure 门户手动扩展集群

    图 4.15:手动扩展集群

  2. 这将打开一个弹出窗口,提供扩展集群的选项。在我们的示例中,我们将把集群缩小到一个节点,如 图 4.16 所示:确认新集群大小的弹出窗口

    图 4.16:确认新集群大小的弹出窗口

  3. 点击屏幕底部的 Apply 按钮以保存这些设置。这将导致 Azure 从您的集群中移除一个节点。此过程大约需要 5 分钟完成。您可以通过点击 Azure 门户顶部的通知图标来跟踪进度,如下所示:

点击 Azure 门户中的通知图标查看缩减操作的进度

图 4.17:可以通过 Azure 门户中的通知跟踪集群扩展进度

当此缩减操作完成后,请在此小型集群上重新启动 guestbook 应用:

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

在下一节中,您将扩展 guestbook,以使其无法在这个小型集群上运行。然后,您将配置集群自动扩展器来扩展集群。

使用集群自动扩展器扩展集群

在本节中,您将探索集群自动扩展器。集群自动扩展器将监视集群中的部署,并根据应用程序需求自动扩展集群。集群自动扩展器会监视由于资源不足而无法调度的 pod 数量。您将首先强制部署中出现无法调度的 pod,然后配置集群自动扩展器以自动扩展集群。

为了强制集群资源不足,您将手动扩展 redis-replica 部署。为此,使用以下命令:

kubectl scale deployment redis-replica --replicas 5

您可以通过查看集群中的 pod 来验证该命令是否成功:

kubectl get pods

这应该会显示类似于 图 4.18 所示的输出:

输出显示由于集群资源不足,五个 pod 中有四个处于 Pending 状态

图 4.18:五个 pod 中有四个处于 Pending 状态,表示它们无法调度

如您所见,当前有两个 pod 处于 Pending 状态。在 Kubernetes 中,Pending 状态意味着该 pod 无法调度到节点上。在这种情况下,是由于集群资源不足导致的。

注意

如果您的集群运行在比 DS2v2 更大的虚拟机上,现在可能不会看到处于 Pending 状态的 pod。在这种情况下,您可以增加副本数,直到看到 pod 处于 Pending 状态。

现在你将配置集群自动缩放器以自动扩展集群。与前一部分的手动缩放类似,你可以通过两种方式配置集群自动缩放器。你可以通过 Azure 门户配置它,类似于我们之前手动缩放的方式,或者你可以使用命令行界面(CLI)配置它。在这个例子中,你将使用 CLI 启用集群自动缩放器。以下命令将为你的集群配置集群自动缩放器:

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

此命令配置了在你的集群中的节点池上的集群自动缩放器。它将其配置为具有一个节点的最小值和两个节点的最大值。这将需要几分钟来配置。

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

kubectl get nodes -w

新节点在集群中显示并变为Ready大约需要 5 分钟时间。一旦新节点状态为Ready,你可以通过按下 Ctrl + C 停止监视节点。你应该会看到类似 Figure 4.19 的输出:

显示新节点加入集群的输出

图 4.19:新节点加入集群

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

kubectl get pods

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

显示所有处于运行状态的 pod 的输出

图 4.20:所有 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

注意

前一个示例中的最后一个命令将显示一个错误消息,如果集群已经有两个节点,则会显示The new node count is the same as the current node count.。你可以安全地忽略此错误。

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

升级你的应用程序

使用 Kubernetes 中的部署(deployments)可以使升级应用程序变得简单明了。与任何升级一样,如果出现问题,应该有良好的回滚策略。你可能会在升级过程中遇到大部分问题。云原生应用程序应该能够相对轻松地处理这些问题,如果你有一个非常强大的开发团队,他们秉承 DevOps 原则。

DevOps 状态报告(puppet.com/resources/report/2020-state-of-devops-report/)多年来报告称,软件部署频率较高的公司,其应用程序的可用性和稳定性也更高。这可能看起来有些反直觉,因为进行软件部署会增加问题的风险。然而,通过更频繁地部署,并采用自动化的 DevOps 实践,你可以减少软件部署带来的影响。

你可以通过多种方式更新在 Kubernetes 集群中运行的应用程序。在本节中,你将探索以下几种更新 Kubernetes 资源的方法:

  • 通过更改 YAML 文件进行升级:当你有权限访问完整的 YAML 文件进行更新时,使用这种方法。这可以通过命令行或自动化系统来完成。

  • 使用 kubectl edit 升级:这种方法主要用于集群上的小规模变更。它是一种快速在集群上实时更新配置的方法。

  • 使用 kubectl patch 升级:当你需要脚本化进行 Kubernetes 中某些小的更新,但没有访问完整 YAML 文件的权限时,可以使用这种方法。这可以通过命令行或自动化系统进行。如果你有原始的 YAML 文件,通常更好的做法是编辑 YAML 文件并使用 kubectl apply 应用更新。

  • 使用 Helm 升级:当你的应用程序通过 Helm 部署时,使用这种方法进行升级。

以下章节描述的方法非常适用于无状态应用程序。如果你有任何状态存储在某个地方,请确保在尝试升级应用程序之前备份该状态。

让我们从通过更改 YAML 文件进行的第一次升级开始这节内容。

通过更改 YAML 文件进行升级

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

部署会检测更改(如果有的话),并将运行状态与期望状态进行匹配。让我们看看这是如何实现的:

  1. 从我们的 guestbook 应用程序开始,探索这个例子:

    kubectl apply -f guestbook-all-in-one.yaml
    
  2. 几分钟后,所有的 Pods 应该都在运行。现在我们来进行第一次升级,将服务从 ClusterIP 改为 LoadBalancer,就像你在本章前面所做的那样。不同的是,现在你将编辑 YAML 文件,而不是使用 kubectl edit。使用以下命令编辑 YAML 文件:

    code guestbook-all-in-one.yaml
    

    取消注释该文件中的第 102 行,将 type 设置为 LoadBalancer,并保存文件,如 图 4.21 所示:

    使用 YAML 文件将服务类型从 ClusterIP 改为 LoadBalancer

    图 4.21:在 guestbook-all-in-one YAML 文件中将类型设置为 LoadBalancer

  3. 如以下代码所示,应用更改:

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

    您应该看到类似于图 4.22的输出:

    输出确认服务的前端已更新

    图 4.22:服务的前端已更新

    图 4.22所示,只有在 YAML 文件中更新的对象(在此案例中是服务)在 Kubernetes 上被更新,其他对象保持不变。

  4. 现在您可以使用以下命令获取服务的公共 IP:

    kubectl get service
    

    给它几分钟,您应该会看到显示的 IP,如图 4.23所示:

    使用 kubectl get service 命令显示服务的公共 IP

    图 4.23:输出显示公共 IP

  5. 现在您将进行另一个更改。您将把第 127 行中的前端镜像image: gcr.io/google-samples/gb-frontend:v4降级为以下内容:

    image: gcr.io/google-samples/gb-frontend:v3
    

    可以通过使用此熟悉的命令打开 guestbook 应用程序并进行更改:

    code guestbook-all-in-one.yaml
    
  6. 运行以下命令执行更新并观察 Pod 的变化:

    kubectl apply -f guestbook-all-in-one.yaml && kubectl get pods -w
    

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

    输出显示从新 ReplicaSet 创建的新 Pods

    图 4.24:从新 ReplicaSet 创建的 Pods

    在这里,您可以看到一个新的 Pod 版本被创建(基于新的 ReplicaSet)。一旦新 Pod 启动并准备好,旧的 Pod 之一将被终止。这个创建-终止循环将不断重复,直到只有新的 Pods 在运行。在第五章,处理 AKS 中的常见故障中,您将看到一个升级失败的示例,并且您会看到 Kubernetes 不会继续升级过程,直到新 Pod 健康运行。

  7. 运行kubectl get events | grep ReplicaSet将显示部署使用的滚动更新策略,该策略用于更新前端镜像:监控 Kubernetes 事件并过滤仅查看与 ReplicaSet 相关的事件

    kubectl get replicaset
    

    这将显示图 4.26所示的输出:

    输出显示前端部署有两个 ReplicaSets,一个为 0 个 Pods,另一个为 3 个 Pods

    图 4.26:两个不同的 ReplicaSets

  8. Kubernetes 还会保留您的滚动更新历史记录。您可以使用此命令查看滚动更新历史:

    kubectl rollout history deployment frontend
    

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

    显示部署的滚动更新历史

    图 4.27:应用程序的部署历史

  9. 由于 Kubernetes 会保留滚动更新的历史记录,这也启用了回滚功能。让我们对您的部署进行回滚:

    kubectl rollout undo deployment frontend
    

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

    kubectl get replicaset
    

    结果输出如图 4.28所示:

    输出显示旧的 ReplicaSet 包含三个 Pods,新 ReplicaSet 的 Pod 数量缩减至零

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

    如预期的那样,旧的 ReplicaSet 被缩放回三实例,而新的 ReplicaSet 被缩放为零实例。

  10. 最后,运行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. 撤销你在上一步所做的更改。你可以通过使用以下命令来实现:

    git reset --hard
    
  2. 然后,你将部署 guestbook 应用程序:

    kubectl create -f guestbook-all-in-one.yaml
    
  3. 要开始编辑,执行以下命令:

    kubectl edit service frontend
    
  4. 这将打开一个vi环境。导航到当前显示为type: ClusterIP(第 27 行)的行,并将其更改为type: LoadBalancer,如图 4.29所示。要进行修改,按下I键,输入更改内容,按下Esc键,输入:wq!,然后按Enter保存更改:显示部署的回滚历史

    图 4.29:将这一行更改为type: LoadBalancer

  5. 一旦保存了更改,你可以通过观察服务对象,直到公网 IP 变为可用。为此,可以输入以下命令:

    kubectl get svc -w
    
  6. 显示更新后的 IP 可能需要几分钟时间。一旦看到正确的公网 IP,你可以通过按下Ctrl + C退出watch命令。

这是一个使用kubectl edit命令对 Kubernetes 对象进行修改的示例。该命令将打开一个文本编辑器,以交互的方式进行修改。这意味着你需要与文本编辑器进行交互来进行更改。这个方法不适用于自动化环境。要进行自动化修改,可以使用kubectl patch命令。

使用 kubectl patch 升级应用程序

在前面的示例中,你使用了文本编辑器来修改 Kubernetes。在这个示例中,你将使用kubectl patch命令来对 Kubernetes 中的资源进行修改。patch命令在自动化系统中尤其有用,特别是当你无法访问集群中部署的原始 YAML 文件时。例如,在脚本或持续集成/持续部署系统中都可以使用此命令。

使用kubectl patch有两种主要方式:一种是创建一个包含更改的文件(称为补丁文件),另一种是直接在命令行内提供更改。这里将解释这两种方法。首先,在这个示例中,你将使用补丁文件将前端的图像从v4更改为v3

  1. 从创建一个名为frontend-image-patch.yaml的文件开始这个示例:

    code frontend-image-patch.yaml
    
  2. 使用以下文本作为该文件中的补丁:

    spec:
      template:
        spec:
          containers:
          - name: php-redis
            image: gcr.io/google-samples/gb-frontend:v3
    

    这个补丁文件使用与典型 YAML 文件相同的 YAML 布局。补丁文件的关键之处在于,它只需包含更改,而不必具备部署整个资源的能力。

  3. 要应用补丁,请使用以下命令:

    kubectl patch deployment frontend \
      --patch "$(cat frontend-image-patch.yaml)"
    

    该命令执行两项操作:首先,它使用cat命令读取frontend-image-patch.yaml文件,然后将其传递给kubectl patch命令以执行更改。

  4. 你可以通过描述前端部署并查找Image部分来验证更改:

    kubectl describe deployment frontend
    

    这将显示如下输出:

    运行 kubectl describe deployment frontend 命令以确认我们是否正在运行旧图像

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

    这是使用补丁文件执行patch命令的一个示例。你也可以直接在命令行中应用补丁,而无需创建 YAML 文件。在这种情况下,你将以 JSON 格式描述更改,而不是 YAML 格式。

    让我们演示一个示例,在其中我们将图像更改恢复为v4

  5. 运行以下命令将图像补丁回v4版本:

    kubectl patch deployment frontend \
    --patch='
    {
        "spec": {
            "template": {
                "spec": {
                    "containers": [{
                        "name": "php-redis",
                        "image": "gcr.io/google-samples/gb-frontend:v4"
                    }]
                }
            }
        }
    }'
    
  6. 你可以通过描述部署并查找Image部分来验证这一更改:

    kubectl describe deployment frontend
    

    这将显示在图 4.31中所示的输出:

运行 kubectl describe deployment frontend 命令以确认我们是否正在运行新图像

图 4.31:再次补丁后,我们正在运行新版本

在进入下一个示例之前,让我们从集群中移除 guestbook 应用程序:

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

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

使用 Helm 升级应用程序

本节将解释如何使用 Helm 运维进行升级:

  1. 运行以下命令:

    helm install wp bitnami/wordpress
    

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

    kubectl describe statefulset wp-mariadb | grep Image
    

    在写作时,图像版本为10.5.8-debian-10-r46,如下所示:

    显示当前 StatefulSet 图像版本的输出

    kubectl get secret wp-mariadb -o yaml
    

    这将生成在图 4.33中所示的输出:

    显示加密的机密信息,即 MariaDB 密码的输出

    echo "<password>" | base64 -d
    

    这将显示解码后的根密码和解码后的数据库密码,如图 4.34所示:

    输出显示未加密的 MariaDB 密码版本

    kubectl get secret wp-wordpress -o yaml
    echo "<WordPress password>" | base64 -d
    
  2. 您可以使用 Helm 更新镜像标签,然后通过以下命令查看 Pod 的变化:

    helm upgrade wp bitnami/wordpress \
    --set mariadb.image.tag=10.5.8-debian-10-r44\
    --set mariadb.auth.password="<decoded password>" \
    --set mariadb.auth.rootPassword="<decoded password>" \
    --set wordpressPassword="<decoded password>" \
    && kubectl get pods -w
    

    这将更新 MariaDB 的镜像并启动一个新的 Pod。您应该会看到类似于图 4.35的输出,其中可以看到前一个版本的数据库 Pod 被终止,新 Pod 启动:

    输出显示前一个 MariaDB Pod 被终止并且一个新的 Pod 启动

    kubectl describe pod wp-mariadb-0 | grep Image
    

    这将生成一个输出,如图 4.36所示:

    输出显示新镜像版本

    图 4.36:显示新镜像

  3. 最后,通过运行以下命令进行清理:

    helm delete wp
    kubectl delete pvc --all
    kubectl delete pv --all
    

您现在已经学会了如何使用 Helm 升级应用程序。正如您在这个例子中看到的,使用 Helm 升级可以通过使用 --set 操作符来完成。这使得使用 Helm 执行升级和多次部署变得更加高效。

总结

本章涵盖了关于构建可扩展应用程序的大量信息。目标是向您展示如何使用 Kubernetes 来扩展部署,方法是创建应用程序的多个实例。

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

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

我们通过研究不同的方式来升级已部署的应用程序结束了本章内容:首先,通过手动更新 YAML 文件,然后学习了两个额外的 kubectl 命令(editpatch),这些命令可以用来进行更改。最后,我们了解了如何使用 Helm 来执行这些升级。

在下一章中,我们将讨论在将应用程序部署到 AKS 时可能遇到的几种常见故障及其解决方法。

第六章:5. 处理 AKS 中的常见故障

Kubernetes 是一个分布式系统,包含许多工作组件。AKS 为你抽象了大部分内容,但了解如何在故障发生时找到问题所在并作出响应,仍然是你的责任。大部分故障处理是由 Kubernetes 自动完成的;然而,你仍会遇到需要手动干预的情况。

在部署在 AKS 上的应用中,有两个方面可能会出现问题。要么是集群本身出现问题,要么是部署在集群上的应用出现问题。本章专注于集群问题。集群可能会出现几种故障。

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

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

另一个常见问题是存储挂载问题,这通常发生在节点不可用时。当 Kubernetes 中的节点变得不可用时,Kubernetes 不会卸载与该故障节点附加的磁盘。这意味着这些磁盘无法被其他节点上的工作负载使用。你将看到一个实际示例,并学习如何从这种故障中恢复。

本章将深入探讨以下主题:

  • 处理节点故障

  • 解决资源不足故障

  • 处理存储挂载问题

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

注意:

请参考 Kubernetes the Hard Way(github.com/kelseyhightower/kubernetes-the-hard-way),这是一个很好的教程,可以帮助你了解 Kubernetes 构建的基础。对于 Azure 版本,请参考 Kubernetes the Hard Way – Azure 翻译版(github.com/ivanfioravanti/kubernetes-the-hard-way-on-azure)。

处理节点故障

无论是有意(为了节省成本)还是无意中,节点可能会宕机。当这种情况发生时,你不希望接到凌晨三点的电话,告诉你系统宕机了。Kubernetes 可以自动处理在故障节点上迁移工作负载。在本练习中,你将部署一个访客留言簿应用,并将集群中的一个节点关闭,以观察 Kubernetes 如何响应:

  1. 确保你的集群至少有两个节点:

    kubectl get nodes
    

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

    创建集群中节点列表

    图 5.1: 集群中的节点列表

    如果你的集群中没有两个节点,请在 Azure 门户中查找你的集群,导航到节点池,选择要扩展的池,并点击“缩放”。然后你可以将节点计数器缩放到 2 节点,如图 5.2所示:

    使用 Azure 门户将集群大小扩展到两个节点

    图 5.2: 扩展集群

  2. 作为本节的示例应用程序,请部署访客留言应用程序。部署此应用程序的 YAML 文件已在本章的源代码中提供(guestbook-all-in-one.yaml)。要部署访客留言应用程序,请使用以下命令:

    kubectl create -f guestbook-all-in-one.yaml
    
  3. 监视 service 对象,直到公共 IP 可用为止。为此,请输入以下命令:

    kubectl get service -w
    

    注意

    你也可以通过使用 kubectl get svc 而不是完整的 kubectl get service 来获取 Kubernetes 中的服务。

  4. 这将花费几秒钟时间来显示更新后的外部 IP。图 5.3显示了服务的公共 IP。一旦你看到公共 IP 出现(在本例中为 20.72.244.113),你可以通过按下Ctrl + C退出 watch 命令:获取服务对象的外部 IP

    图 5.3: 前端服务的外部 IP 从 变为实际 IP 地址

  5. 访问 http://<EXTERNAL-IP>(在本例中为 http://20.72.244.113),如图 5.4所示:使用外部 IP 浏览访客留言应用程序

    图 5.4: 浏览访客留言应用程序

  6. 让我们看看当前运行的 Pod 使用以下命令:

    kubectl get pods -o wide
    

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

    运行在节点 0 和节点 2 上的 Pod 列表

    图 5.5: Pod 分布在节点 0 和节点 2 之间

    这显示你应该将工作负载分布在节点 0 和节点 2 之间。

    注意

    图 5.5中的示例中,工作负载分布在节点 0 和节点 2 之间。你可能注意到这里缺少节点 1。如果你按照第四章,构建可扩展应用程序的示例操作,你的集群应该处于类似状态。这是因为 Azure 在从集群中移除旧节点并添加新节点时(正如你在第四章,构建可扩展应用程序中所做的),它会不断增加节点计数器。

  7. 在引入节点故障之前,你可以执行两个可选步骤来验证你的应用程序是否能够继续运行。你可以每 5 秒运行以下命令,以访问访客留言前端并获取 HTML。建议在新的 Cloud Shell 窗口中打开此命令:

    while true; do 
      curl -m 1 http://<EXTERNAl-IP>/; 
      sleep 5;
    done
    

    注意

    前述命令将持续调用你的应用程序,直到你按下Ctrl + C。可能会有间歇性时间,你将不会收到回复,这在 Kubernetes 重新平衡系统时是正常的。

    你还可以添加一些留言本条目,以查看当你使节点关闭时它们会发生什么。这将显示如下所示的输出,见图 5.6

    在留言本应用中添加几条条目

    图 5.6:在留言本中写入几条信息

  8. 在这个示例中,你正在探索 Kubernetes 如何处理节点故障。为了演示这一点,关闭集群中的一个节点。你可以关闭任何一个节点,虽然为了达到最大影响,建议关闭第 6 步中托管了最多 pods 的节点。在这个示例中,节点 2 将被关闭。

    要关闭这个节点,查找kubectl get nodes,你会看到节点 2 处于 NotReady 状态。在 Kubernetes 中有一个配置项叫做pod-eviction-timeout,它定义了系统等待多长时间以便在健康的主机上重新调度 pods。默认值为 5 分钟。

  9. 如果你在第 7 步中记录了多个留言本条目,回到留言本应用的公共 IP 地址时,你会发现所有的留言都不见了!这表明在节点故障时,任何希望能持久保存的数据都应该使用持久卷声明PVCs),而我们的应用程序中并没有这样做。你将在本章的最后一节看到一个关于此的示例。

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

解决资源耗尽故障

Kubernetes 集群中另一个常见的问题是集群资源耗尽。当集群没有足够的 CPU 或内存来调度额外的 pods 时,pods 将会进入Pending状态。你在第四章,构建可扩展应用程序中也看到了这种行为。

Kubernetes 使用请求来计算某个 pod 所需的 CPU 或内存量。留言本应用对所有的部署都定义了请求。如果你打开文件夹Chapter05中的guestbook-all-in-one.yaml文件,你会看到如下关于redis-replica部署的内容:

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

本节解释了redis-replica部署中的每个 pod 需要200m的 CPU 核心(200毫或20%)和100MiB(兆字节)的内存。在你有 2 个 CPU 的集群(且节点 1 已关闭)的情况下,将其扩展到 10 个 pods 会导致可用资源的问题。我们来看看这个情况:

注意

在 Kubernetes 中,你可以使用二进制前缀表示法或十进制表示法来指定内存和存储。二进制前缀表示法意味着使用 KiB(千字节)表示 1,024 字节,MiB(兆字节)表示 1,024 KiB,Gib(吉字节)表示 1,024 MiB。十进制表示法意味着使用 kB(千字节)表示 1,000 字节,MB(兆字节)表示 1,000 kB,GB(千兆字节)表示 1,000 MB。

  1. 让我们先将 redis-replica 部署扩展到 10 个 pods:

    kubectl scale deployment/redis-replica --replicas=10
    
  2. 这将导致创建几个新的 pods。我们可以使用以下命令检查我们的 pods:

    kubectl get pods
    

    这将生成如下所示的输出,类似于 图 5.10

    由于资源不足,Redis 副本 pod 处于 Pending 状态

    图 5.10:一些 pods 处于 Pending 状态

    这里突出显示的是一个处于 Pending 状态的 pod。这通常发生在集群资源不足时。

  3. 我们可以使用以下命令获取有关这些待处理 pod 的更多信息:

    kubectl describe pod redis-replica-<pod-id>
    

    这将显示更多详细信息。在 describe 命令的底部,你应该看到类似于 图 5.11 所示的内容:

    使用 kubectl describe pod 命令获取有关待处理 pod 的更多详情

    图 5.11:Kubernetes 无法调度此 pod

    它解释了两件事:

    • 其中一个节点的 CPU 资源不足。

    • 其中一个节点有一个污点(node.kubernetes.io/unreachable),该 pod 没有容忍这个污点。这意味着处于 NotReady 状态的节点无法接受 pods。

  4. 我们可以通过启动节点 2 来解决这个容量问题,如 图 5.12 所示。这可以通过类似于关闭过程的方式来完成:从选定的 VMSS 的实例窗格重新启动节点 2

    图 5.12:重新启动节点 2

  5. 在 Kubernetes 中,另一个节点重新变为可用需要几分钟时间。你可以通过执行以下命令来监控 pods 的进度:

    kubectl get pods -w
    

    这将在几分钟后显示类似于 图 5.13 的输出:

    监控 pods 从 Pending 状态到 Running 状态的过渡

    图 5.13:Pods 从 Pending 状态转变为 ContainerCreating 再到 Running

    在这里,你再次看到容器状态从 Pending 变为 ContainerCreating,再到最后的 Running。

  6. 如果你重新执行前一个 pod 的 describe 命令,你将看到类似于 图 5.14 所示的输出:

显示 Kubernetes 调度器将 redis replica pod 分配到节点 2 的输出

图 5.14:当节点再次可用时,待处理的 pods 会被分配到该节点

这表明在节点 2 可用后,Kubernetes 将该 pod 调度到该节点,并启动了容器。

在本节中,你学习了如何诊断资源不足错误。你通过向集群中添加另一个节点解决了该错误。在进入最终的故障模式之前,请清理 guestbook 部署。

注意

第四章,构建可扩展应用程序 中,介绍了 集群自动扩缩器。集群自动扩缩器会监控资源不足错误,并自动向集群添加新节点。

让我们通过运行以下 delete 命令清理 guestbook 部署:

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

现在也可以安全地关闭你之前打开的另一个 Cloud Shell 窗口。

到目前为止,您已经学习了如何从 Kubernetes 集群中的两种节点故障模式中恢复。首先,您看到 Kubernetes 如何处理节点离线,并将 Pod 重新调度到工作节点。之后,您看到了 Kubernetes 如何使用请求来管理 Pod 在节点上的调度,并且当集群资源不足时会发生什么。在接下来的部分中,您将了解 Kubernetes 遇到存储挂载问题时会发生什么。

修复存储挂载问题

在本章的前面,您注意到当 Redis 主节点移动到另一个节点时,访客留言应用程序丢失了数据。这是因为该示例应用程序没有使用任何持久存储。在本节中,您将看到一个示例,说明当 Kubernetes 将附有 PVC 的 Pod 移动到另一个节点时会发生的常见错误,并学习如何修复这些错误。

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

kubectl get all

这应该只显示一个 Kubernetes 服务,如图 5.15 所示:

使用 kubectl get all 命令检查集群的状态

图 5.15:目前您只应该有一个运行中的 Kubernetes 服务

让我们确保两个节点都在运行并处于 Ready 状态:

kubectl get nodes

这应该显示我们两个节点都处于 Ready 状态,如图 5.16 所示:

使用 kubectl get nodes 命令检查两个节点的状态

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

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

要启动此示例,请设置 WordPress 安装。

启动 WordPress 安装

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

如果您在之前的章节中还没有执行此操作,请添加 Bitnami 的 Helm 仓库:

helm repo add bitnami https://charts.bitnami.com/bitnami

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

helm install wp bitnami/wordpress

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

kubectl get pods -w

几分钟后,这应该显示两个 Pod 都处于 Running 状态,并且两个 Pod 的 Ready 状态为 1/1,如图 5.17 所示:

使用 kubectl get pods -w 跟踪 WordPress 安装的进度

图 5.17:几分钟后,所有的 Pod 都将处于运行状态

你可能注意到,wp-wordpress pod 曾经历过错误状态,并在之后重启。这是因为 wp-mariadb pod 没有及时准备好,导致 wp-wordpress pod 进行了重启。你将在第七章,监控 AKS 集群和应用程序中学到更多关于就绪性的信息,以及它如何影响 pod 重启。

在本节中,你学习了如何安装 WordPress。接下来,你将了解如何使用持久化卷避免数据丢失。

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

持久卷PV)是在 Kubernetes 集群中存储持久数据的方式。有关 PV 的详细讨论,请参考第三章,AKS 上的应用部署。让我们一起探讨为 WordPress 部署创建的 PV:

  1. 你可以使用以下命令获取 PersistentVolumeClaims:

    kubectl get pvc
    

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

    使用 kubectl get pvc 命令获取 PersistentVolumeClaims 的详细信息

    kubectl get pv
    

    这将显示你两个 PersistentVolumes:

    使用 kubectl get pv 命令检查已创建的 PersistentVolumes

    kubectl describe pv <pv name>
    

    这将显示该卷的详细信息,如图 5.20所示:

    使用 kubectl describe pv<pv 名称> 命令获取特定 PersistentVolumes 的详细信息

    图 5.20:某个 PV 的详细信息

    这里,你可以看到哪个 PVC 声明了这个卷以及 Azure 中的磁盘名称是什么。

  2. 验证你的网站是否正常工作:

    kubectl get service
    

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

    获取我们 WordPress 网站的公网 IP

    图 5.21:WordPress 网站的公网 IP

  3. 如果你还记得第三章,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.22所示:

使用 Helm 命令获取用户名和密码,以便登录 WordPress 网站

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

你可以通过以下地址登录我们的网站:http://<external-ip>/admin。使用上一步获取的凭证登录。然后,你可以开始为你的网站添加一篇帖子。点击“写下你的第一篇博客文章”按钮,然后创建一个简短的帖子,如图 5.23所示:

在 WordPress 网站上写下你的第一篇博客文章

图 5.23:写下你的第一篇博客文章

现在输入一些文本并点击发布按钮,如图 5.24所示。文本本身并不重要;你是写这篇文章来验证数据是否确实持久化到磁盘:

使用发布按钮将带有随机文本的帖子发布到 WordPress 网站

图 5.24:发布一篇带有随机文本的帖子

如果你现在访问网站的主页 http://<external-ip>,你会看到你的测试文章,如图 5.25所示:

使用网站的外部 IP 导航到 WordPress 网站并验证已发布的文章

图 5.25:已发布的博客文章出现在主页上

在本节中,你部署了一个 WordPress 网站,登录了你的 WordPress 网站,并创建了一个文章。你将在下一节中验证该文章是否能在节点故障时存活。

处理涉及 PVC 的 Pod 故障

你对 PVC 进行的第一个测试是杀死 Pod,并验证数据是否确实已持久化。为此,让我们做两件事:

  1. 监控你应用程序中的 Pod:为此,请使用当前的 Cloud Shell 并执行以下命令:

    kubectl get pods -w
    
  2. 你之前执行的 watch 命令。你应该看到类似图 5.27所示的输出:Kubernetes 创建新 Pod 以从 Pod 故障中恢复,该故障是由于删除 Pod 引起的

    图 5.27:删除 Pod 后,Kubernetes 会自动重新创建这两个 Pod

    如你所见,两个原始 Pod 进入了 Terminating 状态。Kubernetes 很快开始创建新的 Pod 以恢复 Pod 故障。Pod 经历了与原始 Pod 类似的生命周期,从 Pending 到 ContainerCreating,再到 Running。

  3. 如果你访问你的网站,你应该看到你的演示文章已经持久化。这就是 PVC 如何帮助你防止数据丢失的方式,因为它们持久化了本应不会在 Pod 中持久化的数据。

在本节中,你了解了 PVC 如何帮助当 Pod 在同一节点上重新创建时。在下一节中,你将看到当节点发生故障时,PVC 是如何使用的。

处理涉及 PVC 的节点故障

在之前的示例中,你看到 Kubernetes 如何处理具有 PV 附加的 Pod 故障。在本示例中,你将了解 Kubernetes 如何处理附加卷的节点故障。

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

    kubectl get pods -o wide
    

    图 5.28中所示的示例中,节点 2 托管了 MariaDB,节点 0 托管了 WordPress 网站:

    检查托管你应用程序的节点

    图 5.28:检查托管 WordPress 网站的节点

  2. 引入故障并通过 Azure 门户停止托管 WordPress Pod 的节点。你可以按照之前的示例中的相同方式进行操作。首先,查找支持你集群的扩展集,如图 5.29所示:在 Azure 搜索栏中搜索 vmss,并选择集群使用的扩展集

    图 5.29:查找托管你集群的扩展集

  3. 然后,通过点击左侧菜单中的“Instances”,选择需要关闭的节点并点击停止按钮,关闭节点,如 图 5.30 所示:通过集群使用的缩放集的实例面板关闭目标节点

    图 5.30:关闭节点

  4. 执行此操作后,再次观察 pod,查看集群中发生了什么:

    kubectl get pods -o wide -w
    

    如前例所示,Kubernetes 将需要 5 分钟的时间才会开始对失败的节点采取行动。你可以在 图 5.31 中看到这一过程:

    pod 状态显示其处于 ContainerCreating 状态

    图 5.31:处于 ContainerCreating 状态的 pod

  5. 这里出现了一个新的问题。新的 pod 被卡在了 ContainerCreating 状态。让我们来弄清楚发生了什么。首先,描述这个 pod:

    kubectl describe pods/wp-wordpress-<pod-id>
    

    你将看到如下所示的输出,类似于 图 5.32

    使用 kubectl describe 命令查看处于 ContainerCreating 状态的 pod 问题

    图 5.32:解释 pod 处于 ContainerCreating 状态的输出

    这表明卷存在问题。你看到与该卷相关的两个错误:FailedAttachVolume 错误说明该卷已经被另一个 pod 使用,而 FailedMount 错误说明当前 pod 无法挂载该卷。你可以通过手动强制删除卡在 Terminating 状态的旧 pod 来解决此问题。

    注意

    pod 处于 Terminating 状态的行为并不是一个 bug。这是 Kubernetes 的默认行为。Kubernetes 文档中说明如下:“Kubernetes(版本 1.5 或更高)不会仅仅因为节点不可达而删除 pod。运行在不可达节点上的 pod 会在超时后进入 Terminating 或 Unknown 状态。当用户尝试在不可达节点上优雅删除 pod 时,pod 也可能进入这些状态。” 你可以在 kubernetes.io/docs/tasks/run-application/force-delete-stateful-set-pod/ 阅读更多内容。

  6. 要强制从集群中移除终止中的 pod,请使用以下命令获取完整的 pod 名称:

    kubectl get pods
    

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

    获取处于 Terminating 状态的 pod 名称

    图 5.33:获取处于 Terminating 状态的 pod 名称

  7. 使用 pod 的名称强制删除此 pod:

    kubectl delete pod wordpress-wp-<pod-id> --force
    
  8. 在 pod 被删除后,其他 pod 进入 Running 状态大约需要几分钟时间。你可以使用以下命令监控 pod 的状态:

    kubectl get pods -w
    

    这将返回类似于 图 5.34 的输出:

    新的 WordPress pod 返回到 Running 状态

    图 5.34:新的 WordPress pod 返回到 Running 状态

  9. 如您所见,这使得新 Pod 恢复到了健康状态。系统确实花了几分钟时间来识别更改并将卷挂载到新 Pod。让我们使用以下命令再次查看 Pod 的详细信息:

    kubectl describe pod wp-wordpress-<pod-id>
    

    这将生成如下输出:

    新 Pod 现在正在挂载卷并拉取容器镜像

    图 5.35:新 Pod 现在正在挂载卷并拉取容器镜像

  10. 这表明新 Pod 成功地挂载了卷,并且容器镜像已经被拉取。这也使您的 WordPress 网站重新可用,您可以通过访问公共 IP 来验证这一点。在继续下一章之前,请使用以下命令清理应用程序:

    helm delete wp
    kubectl delete pvc --all
    kubectl delete pv --all
    
  11. 让我们也启动已关闭的节点:返回 Azure 门户中的规模集面板,点击左侧菜单中的实例,选择需要启动的节点,然后点击“启动”按钮,如图 5.36所示:

使用所选 VMSS 的实例面板启动已关闭的节点

图 5.36:重新启动节点 0

在本节中,您学习了如何在 PVC 未能挂载到新 Pod 时从节点故障中恢复。您所需要做的就是强制删除卡在Terminating状态的 Pod。

总结

在本章中,您了解了 Kubernetes 中的常见故障模式以及如何从中恢复。本章从 Kubernetes 如何自动检测节点故障并启动新 Pod 以恢复工作负载的示例开始。接着,您扩展了工作负载,并让集群资源耗尽。您通过重新启动故障节点并为集群添加新资源来恢复了这种情况。

接下来,您看到如何使用 PVs 将数据存储在 Pod 外部。您删除了集群中的所有 Pod,并查看了 PVs 如何确保您的应用程序中的数据没有丢失。在本章的最后一个示例中,您看到了如何在 PVs 被挂载时从节点故障中恢复。您通过强制删除终止中的 Pod 成功恢复了工作负载。这使得您的工作负载恢复到了健康状态。

本章已经解释了 Kubernetes 中的常见故障模式。在下一章中,我们将为我们的服务引入 HTTPS 支持,并介绍与 Azure Active Directory 的认证。

第七章:6. 使用 HTTPS 保护你的应用程序

HTTPS 已成为任何面向公众网站的必备条件。它不仅提高了你网站的安全性,而且在新的浏览器功能中也逐渐成为一种要求。HTTPS 是 HTTP 协议的安全版本。HTTPS 使用传输层安全性TLS)证书加密终端用户与服务器之间,或两个服务器之间的流量。TLS 是安全套接字层SSL)的继任者。术语 TLSSSL 经常被交替使用。

过去,你需要从证书颁发机构CA)购买证书,然后将其设置在 Web 服务器上,并定期更新。虽然今天仍然可以这样做,但Let's Encrypt 服务和 Kubernetes 中的辅助工具使得在集群中设置经过验证的 TLS 证书变得非常容易。Let's Encrypt 是一个由互联网安全研究小组ISRG)运营并得到多家公司支持的非营利组织。它是一个免费的服务,提供自动化的经过验证的 TLS 证书。自动化是 Let's Encrypt 服务的一个关键优势。

在 Kubernetes 辅助工具方面,你将了解一个新对象,称为Ingress,并使用一个 Kubernetes 插件,叫做 cert-manager。Ingress 是 Kubernetes 中的一个对象,用于管理外部访问服务,通常用于 HTTP 服务。Ingress 在我们在第三章《应用程序部署在 AKS 上》中解释的服务对象之上添加了额外的功能。它可以配置为处理 HTTPS 流量。它还可以配置为根据主机名将流量路由到不同的后端服务,主机名是由域名系统DNS)分配的,用于连接。

cert-manager 是一个 Kubernetes 插件,帮助自动化创建 TLS 证书。当证书即将过期时,它还帮助进行证书的轮换。cert-manager 可以与 Let's Encrypt 接口,自动请求证书。

在本章中,你将看到如何将 Azure 应用网关设置为 Kubernetes Ingress,并使用 cert-manager 与 Let's Encrypt 进行接口对接。

本章将涵盖以下主题:

  • 设置 Azure 应用网关作为 Kubernetes Ingress

  • 在服务前设置 Ingress

  • 向 Ingress 添加 TLS 支持

让我们从设置 Azure 应用网关作为 AKS 的 Ingress 开始。

设置 Azure 应用网关作为 Kubernetes Ingress

在 Kubernetes 中,Ingress 是一个对象,用于将来自集群外部的 HTTP 和 HTTPS 流量路由到集群中的服务。通过 Ingress 暴露服务,而不是像到目前为止那样直接暴露它们,有许多优势。这些优势包括能够将多个主机名路由到同一个公共 IP 地址,并将 TLS 终止从实际应用程序卸载到 Ingress。

要在 Kubernetes 中创建一个入口(ingress),你需要安装一个入口控制器。入口控制器是能够在 Kubernetes 中创建、配置和管理入口的软体。Kubernetes 并没有预装入口控制器。入口控制器有多种实现方式,完整的列表可以在此网址查看:kubernetes.io/docs/concepts/services-networking/ingress-controllers/

在 Azure 中,应用程序网关是一个第七层负载均衡器,可以通过使用 应用程序网关入口控制器(AGIC) 将其作为 Kubernetes 的入口。第七层负载均衡器是工作在应用层的负载均衡器,而应用层是 OSI 网络参考模型中的第七层,也是最高层。Azure 应用程序网关拥有许多高级功能,如自动扩展和 Web 应用防火墙(WAF)

配置 AGIC 有两种方式:使用 Helm 或作为 Azure Kubernetes Service (AKS) 附加组件。通过 AKS 附加组件功能安装 AGIC,将得到一个微软支持的配置。此外,使用附加组件的部署方式会由微软自动更新,确保你的环境始终保持最新。

在这一节中,你将创建一个新的应用程序网关实例,使用附加组件方式设置 AGIC,最后部署一个入口资源以暴露一个应用程序。在本章稍后的部分,你将扩展这个设置,使用 Let's Encrypt 证书加入 TSL。

创建新的应用程序网关

在这一节中,你将使用 Azure CLI 创建一个新的应用程序网关。然后,在下一节中,你将使用此应用程序网关与 AGIC 集成。本节中的不同步骤已在本章的 setup-appgw.sh 文件中的代码示例中进行了总结,这个文件是随书提供的代码示例的一部分。

  1. 为了组织本章中创建的资源,建议你创建一个新的资源组。确保在你部署 AKS 集群的相同位置创建新的资源组。你可以通过以下命令在 Azure CLI 中完成此操作:

    az group create -n agic -l westus2
    
  2. 接下来,你需要创建应用程序网关所需的网络组件。这些组件包括一个带 DNS 名称的公共 IP 和一个新的虚拟网络。你可以使用以下命令来完成此操作:

    az network public-ip create -n agic-pip \
       -g agic --allocation-method Static --sku Standard \
       --dns-name "<your unique DNS name>"
    az network vnet create -n agic-vnet -g agic \
      --address-prefix 192.168.0.0/24 --subnet-name agic-subnet \
      --subnet-prefix 192.168.0.0/24
    

    注意

    az network public-ip create 命令可能会显示一个警告信息 [Coming breaking change] 在即将发布的版本中,当 sku 为 Standard 且未提供区域时,默认行为将发生变化:对于区域性区域,你将获得一个区域冗余的 IP,表示为 zones:["1","2","3"]; 对于非区域性区域,你将获得一个非区域冗余的 IP,表示为 zones:[]。

  3. 最后,你可以创建应用程序网关。此命令的执行可能需要几分钟时间。

    az network application-gateway create -n agic -l westus2 \
      -g agic --sku Standard_v2 --public-ip-address agic-pip \
      --vnet-name agic-vnet --subnet agic-subnet
    
  4. 应用程序网关的部署需要几分钟时间。一旦创建完成,你可以在 Azure 门户中查看该资源。要查找它,可以在 Azure 搜索栏中搜索 agic(或你为应用程序网关设置的名称),然后选择你的应用程序网关。通过 Azure 门户搜索栏搜索应用程序网关

    图 6.1:在 Azure 搜索栏中查找应用程序网关

  5. 这将显示你的应用程序网关在 Azure 门户中的样子,如 图 6.2 所示:Azure 门户中应用程序网关面板的概览

    图 6.2:Azure 门户中的应用程序网关

  6. 要验证是否已成功创建,浏览到你为公共 IP 地址配置的 DNS 名称。这将显示类似于 图 6.3 的输出。请注意,所显示的错误消息是预期中的,因为你还没有在应用程序网关后面配置任何应用程序。你将在 为 guestbook 应用程序添加 Ingress 规则 一节中使用 AGIC 配置应用程序。

浏览到配置的公共 IP 地址的 DNS 名称,验证我们是否能连接到应用程序网关

图 6.3:验证是否可以连接到应用程序网关

现在你已经创建了一个新的应用程序网关并成功连接,我们将继续将这个应用程序网关与现有的 Kubernetes 集群集成。

配置 AGIC

在本节中,你将使用 AGIC AKS 插件将应用程序网关与 Kubernetes 集群集成。你还将设置虚拟网络对等连接,以便应用程序网关可以将流量发送到你的 Kubernetes 集群。

  1. 要启用集群与应用程序网关之间的集成,请使用以下命令:

    appgwId=$(az network application-gateway \
      show -n agic -g agic -o tsv --query "id") 
    az aks enable-addons -n handsonaks \
      -g rg-handsonaks -a ingress-appgw \
      --appgw-id $appgwId
    
  2. 接下来,你需要将应用程序网关网络与 AKS 网络进行对等连接。要进行网络对等连接,可以使用以下代码:

    nodeResourceGroup=$(az aks show -n handsonaks \
      -g rg-handsonaks -o tsv --query "nodeResourceGroup")
    aksVnetName=$(az network vnet list \
      -g $nodeResourceGroup -o tsv --query "[0].name")
    aksVnetId=$(az network vnet show -n $aksVnetName \
      -g $nodeResourceGroup -o tsv --query "id")
    az network vnet peering create \
      -n AppGWtoAKSVnetPeering -g agic \
      --vnet-name agic-vnet --remote-vnet $aksVnetId \
      --allow-vnet-access
    appGWVnetId=$(az network vnet show -n agic-vnet \
      -g agic -o tsv --query "id")
    az network vnet peering create \
      -n AKStoAppGWVnetPeering -g $nodeResourceGroup \
      --vnet-name $aksVnetName --remote-vnet $appGWVnetId --allow-vnet-access
    

这标志着应用程序网关与 AKS 集群的集成完成。你已经启用了 AGIC 插件,并将两个网络连接在一起。在接下来的章节中,你将使用 AGIC 集成为演示应用程序创建一个 Ingress。

为 guestbook 应用程序添加 Ingress 规则

到目前为止,你已经创建了一个新的应用程序网关,并将其与 Kubernetes 集群集成。在本节中,你将部署 guestbook 应用程序,然后通过 Ingress 将其暴露。

  1. 要启动 guestbook 应用程序,请输入以下命令:

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

    这将创建你在前几章中使用过的 guestbook 应用程序。你应该能看到如 图 6.4 所示的对象正在创建:

    创建我们在前几章中使用过的 guestbook 应用程序

    图 6.4:创建 guestbook 应用程序

  2. 然后,你可以使用以下 YAML 文件通过入口暴露前端服务。这在本章的源代码中提供为 simple-frontend-ingress.yaml

    1   apiVersion: networking.k8s.io/v1
    2   kind: Ingress
    3   metadata:
    4     name: simple-frontend-ingress
    5     annotations:
    6       kubernetes.io/ingress.class: azure/application-gateway
    7   spec:
    8     rules:
    9     - http:
    10        paths:
    11        - path: /
    12          pathType: Prefix
    13          backend:
    14            service:
    15              name: frontend
    16              port:
    17                number: 80
    

    让我们看看这个 YAML 文件中定义了什么:

    • Ingress 对象。

    • azure/application-gateway

    以下几行定义了实际的入口:

    • 第 8-12 行:在这里,你定义了该入口监听的路径。在我们的案例中,这是顶级路径。在更高级的情况下,你可以使用不同的路径指向不同的服务。

    • 第 13-17 行:这些行定义了该流量应指向的实际服务。

    你可以使用以下命令来创建此入口:

    kubectl apply -f simple-frontend-ingress.yaml
    
  3. 如果你现在访问你在创建新应用程序网关部分中创建的 http://dns-name/,你应该会看到如图 6.5所示的输出:通过入口访问 guestbook 应用程序

    图 6.5:通过入口访问 guestbook 应用程序

    注意

    你不需要像前几章那样公开暴露前端服务。你已经将入口作为公开的服务,前端服务仍然对集群保持私有。

    显示公开可访问入口的流程图

    图 6.6:显示公开可访问入口的流程图

  4. 你可以通过运行以下命令来验证这一点:

    kubectl get service
    
  5. 这应该能显示出你没有公共服务,因为在图 6.7中看不到EXTERNAL-IP

确认前端服务保持私有的输出

图 6.7:输出显示你没有公共服务

在本节中,你启动了 guestbook 应用程序的一个实例。然后,你通过创建入口将其公开,从而配置了你之前创建的应用程序网关。只有入口是公开可访问的。

接下来,你将扩展 AGIC 的功能,并学习如何使用 Let's Encrypt 的证书来保护流量。

向入口添加 TLS

现在,你将为应用程序添加 HTTPS 支持。为此,你需要一个 TLS 证书。你将使用 cert-manager Kubernetes 插件来向 Let's Encrypt 请求证书。

注意

尽管本节重点介绍了使用 Let's Encrypt 等自动化服务,但你仍然可以选择传统方式,购买现有证书颁发机构(CA)的证书并将其导入 Kubernetes。有关如何操作的更多信息,请参考 Kubernetes 文档:kubernetes.io/docs/concepts/services-networking/ingress/#tls

这其中涉及几个步骤。将 HTTPS 添加到应用程序的过程如下:

  1. 安装 cert-manager,它与 Let's Encrypt API 进行交互,为你指定的域名请求证书。

  2. 安装证书颁发者,它将从 Let's Encrypt 获取证书。

  3. 为给定的完全限定域名FQDN)创建 SSL 证书。FQDN 是一个完全限定的 DNS 记录,包含顶级域名(例如.org 或.com)。你在第 2 步创建新应用程序网关部分中创建了一个与公共 IP 关联的 FQDN。

  4. 通过使用第 3 步中创建的证书,创建一个入口(ingress)来保护前端服务。在本节的示例中,你不会单独执行这一步。你将重新配置入口,以自动获取在第 3 步中创建的证书。

让我们从在集群中安装cert-manager的第一步开始。

安装 cert-manager

cert-manager (github.com/jetstack/cert-manager) 是一个 Kubernetes 插件,它自动化管理和颁发来自各种颁发源的 TLS 证书。它负责续订证书,并确保证书定期更新。

注意

cert-manager项目不是由微软管理或维护的。它是一个开源解决方案,最初由Jetstack公司管理,最近该公司将其捐赠给了 Cloud Native Computing Foundation。

以下命令将在你的集群中安装cert-manager

kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.2.0/cert-manager.yaml

这将安装集群中的多个组件,如图 6.8所示。有关这些组件的详细解释,请参见cert-manager文档:cert-manager.io/docs/installation/kubernetes/

在集群中安装 cert-manager

图 6.8:在集群中安装 cert-manager

cert-manager利用了 Kubernetes 的一个功能,称为cert-manager,它会创建六个 CRD,之后你将在本章中使用其中的一些。

现在你已经安装了cert-manager,可以进行下一步操作:设置证书颁发者。

安装证书颁发者

在本节中,你将安装 Let's Encrypt 的暂存证书颁发者。证书可以由多个颁发者颁发。例如,letsencrypt-staging是用于测试的。当你构建测试时,会使用暂存服务器。证书颁发者的代码已在本章的源代码中提供,位于certificate-issuer.yaml文件中。像往常一样,使用kubectl create -f certificate-issuer.yaml;该 YAML 文件的内容如下:

1   apiVersion: cert-manager.io/v1
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-mail address>
9       privateKeySecretRef:
10        name: letsencrypt-staging
11      solvers:
12      - http01:
13          ingress:
14            class: azure/application-gateway

让我们看看我们在这里定义了什么:

  • cert-manager已创建。在这种情况下,特别是,你指向Issuer对象。颁发者是你的 Kubernetes 集群与实际的证书颁发机构之间的联系,本例中是 Let's Encrypt。

  • 第 6-10 行:在这里,你提供了 Let's Encrypt 的配置,并指向暂存服务器。

  • 第 11-14 行:这是 ACME 客户端的附加配置,用于认证域名所有权。您将 Let's Encrypt 指向 Azure 应用程序网关入口,以验证您拥有稍后申请证书的域名。

安装证书颁发机构后,您现在可以继续执行下一步:在入口上创建 TLS 证书。

创建 TLS 证书并保护入口

在本节中,您将创建一个 TLS 证书。您可以通过两种方式配置 cert-manager 来创建证书。您可以手动创建证书并将其链接到入口,或者您可以配置入口控制器,使得 cert-manager 自动创建证书。

在这个示例中,您将使用后一种方法配置入口。

  1. 首先,编辑入口文件使其像下面的 YAML 代码一样。此文件在 GitHub 上的源代码中以 ingress-with-tls.yaml 存在:

    1   apiVersion: networking.k8s.io/v1
    2   kind: Ingress
    3   metadata:
    4     name: simple-frontend-ingress
    5     annotations:
    6       kubernetes.io/ingress.class: azure/application-gateway
    7       cert-manager.io/issuer: letsencrypt-staging
    8       cert-manager.io/acme-challenge-type: http01
    9   spec:
    10    rules:
    11    - http:
    12        paths:
    13        - path: /
    14          pathType: Prefix
    15          backend:
    16            service:
    17              name: frontend
    18              port:
    19                number: 80
    20      host: <your dns-name>.<your azure region>.cloudapp.azure.com
    21    tls:
    22      - hosts:
    23        - <your dns-name>.<your azure region>.cloudapp.azure.com 
    24        secretName: frontend-tls 
    

    您应该对原始入口做出以下更改:

    • acme-challenge 以证明域名所有权。

    • 第 20 行:此处添加入口的域名。因为 Let's Encrypt 只为域名颁发证书,所以这是必需的。

    • 第 21-24 行:这是入口的 TLS 配置。它包含主机名以及将创建的用于存储证书的密钥名称。

  2. 您可以使用以下命令更新之前创建的入口:

    kubectl apply -f ingress-with-tls.yaml
    

    cert-manager 大约需要一分钟的时间来请求证书并配置入口以使用该证书。在等待期间,让我们来看看 cert-manager 为您创建的中间资源。

  3. 首先,cert-manager 为您创建了一个 certificate 对象。您可以使用以下命令查看该对象的状态:

    kubectl get certificate
    

    此命令将生成如图 6.9所示的输出:

    显示证书对象状态为未准备好的输出

    图 6.9:证书对象的状态

  4. 如您所见,证书尚未准备好。cert-manager 还创建了另一个对象来实际获取证书。这个对象是 certificaterequest。您可以使用以下命令获取其状态:

    kubectl get certificaterequest
    

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

    显示证书请求对象状态为 False 的输出

    kubectl describe certificaterequest
    

    在等待证书颁发期间,状态将类似于图 6.11所示:

    显示证书请求对象的额外详细信息,显示 cert-manager 正在等待证书颁发

    图 6.11:使用 kubectl describe 命令获取证书请求对象的详细信息

    如您所见,certificaterequest 对象显示了订单已创建并且处于待处理状态。

  5. 几秒钟后,describe 命令应该会返回成功创建证书的消息。运行以下命令获取更新的状态:

    kubectl describe certificaterequest
    

    此命令的输出如图 6.12所示:

    显示成功创建证书消息的输出

    图 6.12:已发放的证书

    这应该使前端入口通过 HTTPS 提供服务。

  6. 让我们在浏览器中试试,通过浏览到你在创建新的应用程序网关部分中创建的 DNS 名称。根据浏览器的缓存,你可能需要在 URL 前加上 https://

  7. 一旦你到达入口点,它会在浏览器中显示错误,提示证书无效,类似于图 6.13。这是可以预期的,因为你正在使用 Let's Encrypt 的暂存服务器:

使用 Let's Encrypt 暂存服务器时,证书默认不被信任

图 6.13:使用 Let's Encrypt 暂存服务器时,证书默认不被信任

你可以通过点击高级并选择继续来浏览到你的应用程序。

在本节中,你成功地为你的入口添加了 TLS 证书,以保护到它的流量。既然你已经能够使用暂存证书完成测试,现在可以继续进入生产系统。

从暂存切换到生产环境

在本节中,你将从暂存证书切换到生产级证书。为此,你可以通过在集群中创建新的发行者来重新进行之前的操作,类似以下内容(该代码在 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-prod
11      solvers:
12      - http01:
13          ingress:
14            class: azure/application-gateway 

然后,在 ingress-with-tls.yaml 文件中将发行者的引用替换为 letsencrypt-prod,如所示(在 ingress-with-tls-prod.yaml 文件中提供):

1   apiVersion: networking.k8s.io/v1
2   kind: Ingress
3   metadata:
4     name: simple-frontend-ingress
5     annotations:
6       kubernetes.io/ingress.class: azure/application-gateway
7       cert-manager.io/issuer: letsencrypt-prod
8       cert-manager.io/acme-challenge-type: http01
9   spec:
10    rules:
11    - http:
12        paths:
13        - path: /
14          pathType: Prefix
15          backend:
16            service:
17              name: frontend
18              port:
19                number: 80
20      host: <your dns-name>.<your azure region>.cloudapp.azure.com
21    tls:
22      - hosts:
23        - <your dns-name>.<your azure region>.cloudapp.azure.com
24        secretName: frontend-prod-tls 

要应用这些更改,请执行以下命令:

kubectl create -f certificate-issuer-prod.yaml
kubectl apply -f ingress-with-tls-prod.yaml

证书再次激活需要大约一分钟的时间。新证书发放后,你可以再次访问你的 DNS 名称,并且不再会看到有关证书无效的警告。如果你点击浏览器中的锁形图标,你应该会看到你的连接是安全的并且使用了有效证书:

显示有效证书的网页

图 6.14:显示有效证书的网页

在本节中,你学习了如何为入口添加 TLS 支持。你通过安装 cert-manager Kubernetes 插件完成了这项工作。cert-manager 从 Let's Encrypt 获得了免费证书,并将其添加到已部署在应用程序网关上的现有入口中。这里描述的过程并非特定于 Azure 和 Azure 应用程序网关。这种为入口添加 TLS 的过程也适用于其他入口控制器。

让我们删除本章中创建的资源:

kubectl delete -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml
az aks disable-addons -n handsonaks \
  -g rg-handsonaks -a ingress-appgw 

总结

在本章中,您为 guestbook 应用程序添加了 HTTPS 安全性,而无需实际更改源代码。您首先设置了一个新的应用程序网关,并在 AKS 上配置了 AGIC。这使您能够创建可以在应用程序网关上配置的 Kubernetes ingress。

接下来,您安装了一个与 Let's Encrypt API 接口的证书管理器,用于请求后续指定的域名证书。您利用证书颁发机构从 Let's Encrypt 获取证书。然后,您重新配置了 ingress,以便在集群中向该证书颁发机构请求证书。借助证书管理器和 ingress 的这些功能,您现在能够通过 TLS 来保护您的网站。

在下一章中,您将学习如何监控您的部署并设置警报。您还将学习如何在错误发生时快速识别根本原因,以及如何调试在 AKS 上运行的应用程序。同时,您将学习在识别根本原因后如何进行正确的修复。

第八章:7. 监控 AKS 集群和应用程序

现在你已经知道如何在 AKS 集群上部署应用程序,让我们集中讨论如何确保你的集群和应用程序保持可用。在本章中,你将学习如何监控集群和运行在其上的应用程序。你将探索 Kubernetes 如何通过就绪探针和存活探针确保你的应用程序可靠运行。

你还将学习AKS 诊断Azure 监视器的使用方法,以及它们如何在 Azure 门户中集成。你将看到如何使用 AKS 诊断来监控集群本身的状态,以及 Azure 监视器如何帮助监控集群中的 Pods,并允许你在大规模上访问 Pods 的日志。

简而言之,本章将涵盖以下主题:

  • 使用kubectl监控和调试应用程序

  • 审查 Kubernetes 报告的指标

  • 审查来自 Azure 监视器的指标

让我们通过回顾一些kubectl命令来开始本章内容,这些命令可以帮助你监控应用程序。

用于监控应用程序的命令

监控部署在 Kubernetes 上的应用程序及其 Kubernetes 基础设施的健康状况,对于向客户提供可靠的服务至关重要。监控有两个主要的使用场景:

  • 持续监控,获取异常警报

  • 故障排除和调试应用程序错误

在观察运行在 Kubernetes 集群上的应用程序时,你需要并行检查多个方面,包括容器、Pods、服务以及集群中的节点。对于持续监控,你需要使用像 Azure 监视器或 Prometheus 这样的监控系统。Azure 监视器将在本章后续介绍。Prometheus(prometheus.io/)是 Kubernetes 生态系统中流行的开源解决方案,用于监控 Kubernetes 环境。对于故障排除,你需要与实时集群进行交互。最常用的故障排除命令如下:

kubectl get <resource type> <resource name>
kubectl describe <resource type> <resource name>
kubectl logs <pod name>

本章稍后将详细描述每个命令。

为了开始实际示例,使用以下命令重新创建 guestbook 示例:

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

create命令运行时,你将观察其在以下部分的进度。让我们先来探索get命令。

kubectl get命令

要查看部署应用程序的整体情况,kubectl提供了get命令。get命令列出你指定的资源。资源可以是 Pods、ReplicaSets、ingresses、节点、部署、机密等。你已经在前几章中运行过此命令,以验证应用程序是否已准备好使用。

执行以下步骤:

  1. 运行以下get命令,获取资源及其状态:

    kubectl get all
    

    这将显示你命名空间中的所有部署、ReplicaSets、Pods 和服务:

    显示默认命名空间中所有资源的输出

    图 7.1:默认命名空间中运行的所有资源

  2. 集中注意力查看你部署中的 pods。你可以使用以下命令获取 pods 的状态:

    kubectl get pods
    

    你将只看到 pods,如 图 7.2 所示。让我们详细查看:

    检查命名空间中所有 pods 的状态

    图 7.2:你命名空间中的所有 pods

    第一列显示 pod 名称,例如 frontend-766d4f77cb-ds6gb。第二列显示 pod 中有多少容器已经准备好,相对于 pod 中容器的总数。就绪状态通过 Kubernetes 中的就绪探针定义。本章稍后会有一个专门的章节介绍 就绪性和存活性探针

    第三列显示状态,例如 PendingContainerCreatingRunning 等。第四列显示重启次数,第五列显示创建 pod 时的年龄。

  3. 如果你需要更多关于 pod 的信息,可以通过在命令中添加 -o wide 来扩展 get 命令的输出,像这样:

    kubectl get pods -o wide
    

    这将显示更多信息,如 图 7.3 所示:

获取命名空间中所有 pod 的额外详情

图 7.3:添加 -o wide 显示 pods 的更多细节

额外的列包括 pod 的 IP 地址、它所运行的节点、提名节点和就绪门控。提名节点仅在高优先级 pod 抢占低优先级 pod 时设置。提名节点字段随后会设置在高优先级 pod 上。这表示在低优先级 pod 平稳终止后,高优先级 pod 将会调度到此节点。就绪门控是一种将外部系统组件引入为 pod 就绪状态的方法。

执行 get pods 命令仅显示当前 pod 的状态。正如我们接下来将看到的,任何状态下都有可能发生失败,我们需要使用 kubectl describe 命令来深入探讨。

kubectl describe 命令

kubectl describe 命令会提供你正在描述的对象的详细视图。它包含了该对象本身的细节,以及与该对象相关的任何最新事件。而 kubectl get events 命令列出了整个命名空间的所有事件,使用 kubectl describe 命令时,你只会看到与特定对象相关的事件。如果你只对 pods 感兴趣,可以使用以下命令:

kubectl describe pods

上述命令列出了与所有 pods 相关的所有信息。通常这些信息过于繁杂,无法在普通的 shell 中显示。

如果你只想查看特定 pod 的信息,可以输入以下命令:

kubectl describe pod/<pod-name>

注意

你可以在 pod<pod-name> 之间使用斜杠或空格。以下两个命令将产生相同的输出:

kubectl describe pod/<pod-name>

kubectl describe pod <pod-name>

你将得到一个类似于 图 7.4 的输出,稍后将详细解释:

描述单个 Pod,以显示该对象的详细输出

图 7.4:描述一个对象,显示该对象的详细输出

从描述中,你可以获取 Pod 运行的节点、运行了多久、其内部 IP 地址、Docker 镜像名称、暴露的端口、env 变量以及事件(来自过去一小时内的事件)。

在前面的例子中,Pod 的名称是 frontend-766d4f77cb-ds6gb。正如 第一章,容器和 Kubernetes 入门 中提到的,它采用 <ReplicaSet 名称>-<随机 5 个字符> 格式。replicaset 名称本身是从前端的部署名称随机生成的:<deployment 名称>-<随机字符串>

图 7.5 显示了部署、ReplicaSet 和 Pod 之间的关系:

描述部署、ReplicaSet 和 Pod 之间关系的流程图

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

该 Pod 运行的命名空间是 default。到目前为止,你一直在使用名为 default 的命名空间。

来自前面输出的另一个重要部分是 node 部分:

Node:         aks-agentpool-39838025-vmss000000/10.240.0.4 

node 部分让你知道 Pod 正在运行的物理节点/虚拟机。如果 Pod 一直在重启或出现运行问题,而其他一切看起来正常,那可能是节点本身的问题。获得这些信息对于进行高级调试至关重要。

以下是 Pod 最初预定的时间:

Start Time:   Tue, 26 Jan 2021 02:10:33 +0000 

这并不意味着 Pod 从那个时间点开始就一直在运行,所以从这个意义上来说,时间可能具有误导性。如果发生健康事件(例如,容器崩溃),Pod 会自动重置。

你可以通过使用 Labels 在 Kubernetes 中添加更多关于工作负载的信息,如下所示:

Labels:app=guestbook
pod-template-hash=57d8c9fb45
tier=frontend

标签是 Kubernetes 中常用的功能。例如,这就是如何在对象之间建立链接的方式,如 servicepoddeploymentReplicaSet 再到 pod图 7.5)。如果你发现流量没有从服务路由到 Pod,这是你首先需要检查的地方。同时,你还会注意到 pod-template-hash 标签也出现在 Pod 名称中。这就是 ReplicaSet 和 Pod 之间链接的方式。如果标签不匹配,资源将无法连接。

以下显示的是 Pod 的内部 IP 和其状态:

Status:       Running
IP:           10.244.0.44
IPs:
  IP:           10.244.0.44 

如前章所述,在构建应用程序时,Pod 可能会迁移到不同的节点并获取不同的 IP,因此应避免使用这些 IP 地址。然而,在调试应用程序问题时,获取 Pod 的直接 IP 有助于排查问题。你可以不通过服务对象连接到应用程序,而是通过直接连接到其他 Pod 的 IP 地址来测试连接性。

在 Pod 中运行的容器以及暴露的端口列在以下代码块中:

Containers:
  php-redis:
    ...
    Image:          gcr.io/google-samples/gb-frontend:v4
    ...
    Port:           80/TCP
    ...
    Requests:
      cpu:     10m
      memory:  10Mi
    Environment:
      GET_HOSTS_FROM:  dns
    ...

在这种情况下,你正在从gcr.io容器注册表获取v4标签的gb-frontend容器,仓库名称是google-samples

端口80暴露给外部流量。由于每个 Pod 都有自己的 IP 地址,即使在同一主机上运行多个相同 Pod 的实例,仍然可以暴露相同的端口。例如,如果你有两个 Pod 在同一节点上运行 Web 服务器,它们都可以使用端口80,因为每个 Pod 都有自己的 IP 地址。这是一个巨大的管理优势,因为你不需要担心在同一节点上的端口冲突。

过去一小时内发生的任何事件都会显示在这里:

Events:

使用kubectl describe非常有助于获取有关你正在运行的资源的更多上下文。最后一部分包含与你描述的对象相关的事件。你可以使用kubectl get events命令获取集群中的所有事件。

要查看系统中所有资源的事件,请运行以下命令:

kubectl get events

注意

Kubernetes 默认只保留 1 小时的事件记录。

如果一切顺利,你应该会看到类似图 7.6的输出:

运行 kubectl get events 命令显示过去一小时的所有事件

图 7.6:获取事件显示过去一小时内的所有事件

图 7.6只显示了一个 Pod 的事件,但正如你在输出中看到的,这个命令的输出包含了最近创建、更新或删除的所有资源的事件。

在这一部分,你已经了解了可以用来检查 Kubernetes 应用程序的命令。在下一部分,你将专注于调试应用程序故障。

调试应用程序

现在你已经基本了解了如何检查应用程序,你可以开始了解如何调试部署中的问题。

在这一部分,将介绍常见错误,并帮助你确定如何调试和修复它们。

如果你还没有实现 Guestbook 应用程序,请运行以下命令:

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

几秒钟后,应用程序应该能够正常运行。

镜像拉取错误

在这一部分,你将通过将镜像标签值设置为一个不存在的标签来引入镜像拉取错误。当 Kubernetes 无法下载所需容器的镜像时,就会发生镜像拉取错误。

  1. 在 Azure Cloud Shell 中运行以下命令:

    kubectl edit deployment/frontend
    

    接下来,执行以下步骤,将镜像标签从v4更改为v_non_existent

  2. 输入/gb-frontend并按下Enter键,将光标带到镜像定义位置。

    按下I键进入插入模式。删除 v4 并输入 v_non_existent,如图 7.7所示:

    使用 Azure Cloud Shell 将镜像标签从 v4 更改为 v_non_existent

    图 7.7:将镜像标签从 v4 更改为 v_non_existent

  3. 现在,首先按下Esc键关闭编辑器,然后输入:wq!并按Enter键。

  4. 运行以下命令以列出当前命名空间中的所有 pod:

    kubectl get pods
    

    上述命令应该会显示错误,如图 7.8所示:

    显示当前命名空间中所有 pod 的状态错误

    图 7.8:其中一个 pod 的状态为 ErrImagePull 或 ImagePullBackOff

    你可能会看到 ErrImagePullImagePullBackOff 状态。这两种错误都表示 Kubernetes 无法从注册表中拉取镜像。ErrImagePull 错误正是描述这一点;ImagePullBackOff 则表示 Kubernetes 会在重试下载镜像之前等待,这种回退有一个指数级的延迟,从 10 秒到 20 秒到 40 秒,以此类推,最多可达 5 分钟。

  5. 运行以下命令获取完整的错误细节:

    kubectl describe pods/<failed pod name>
    

    一个示例错误输出显示在图 7.9中,关键的错误信息用红色标出:

    描述单个 pod 显示更多的错误细节

    图 7.9:使用 describe 显示更多的错误细节

    事件清楚地显示镜像不存在。将无效凭证传递给私有 Docker 仓库等错误也会在此显示。

  6. 通过将镜像标签设置回v4来修复错误。首先,在 Cloud Shell 中输入以下命令以编辑部署:

    kubectl edit deployment/frontend
    
  7. 输入/gb-frontend并按下Enter键,将光标带到镜像定义位置。

  8. 按下I键进入插入模式。删除 v_non_existent,并输入 v4

  9. 现在,首先按下Esc键关闭编辑器,然后输入:wq!并按Enter键。

  10. 这应该会自动修复部署。你可以通过再次获取 pod 的事件来验证它。

    注意

    由于 Kubernetes 进行了滚动更新,前端服务保持持续可用,且没有停机时间。Kubernetes 识别到新规范中的问题,并停止自动推送额外的更改。

镜像拉取错误可能会发生在镜像不可用或无法访问容器注册表时。在接下来的章节中,你将探索应用程序本身的错误。

应用程序错误

现在你将看到如何调试应用程序错误。本节中的错误将是自我引发的,类似于上一节。调试该问题的方法与我们用来调试正在运行的应用程序的错误相同。

  1. 首先,获取前端服务的公共 IP:

    kubectl get service 
    
  2. 通过将公共 IP 粘贴到浏览器中来连接到服务。创建几个条目:

在访客留言应用中创建条目

图 7.10:在访客留言应用中添加几个条目

你现在已经有一个运行中的访客留言应用实例。为了改善示例体验,最好缩减前端,使其只运行一个副本。

缩减前端

第三章在 AKS 上部署应用中,你学到了前端部署的配置是replicas=3。这意味着应用接收到的请求可以由任何 pod 处理。为了引入应用错误并记录这些错误,你需要在所有三个 pod 中进行更改。

但为了简化这个示例,将replicas设置为1,这样你只需要对一个 pod 进行更改:

kubectl scale --replicas=1 deployment/frontend

只运行一个副本将使引入错误变得更加容易。现在我们来引入这个错误。

引入应用错误

在这种情况下,你将使提交按钮无法工作。你需要修改应用代码来实现这一点:

注意:

不建议通过使用kubectl exec在 pod 中执行命令来对应用进行生产环境中的更改。如果你需要更改应用,推荐的方式是创建一个新的容器镜像并更新你的部署。

  1. 你将使用kubectl exec命令。此命令允许你在该 pod 的命令行上运行命令。使用-it选项,它将一个交互式终端附加到 pod 上,并为你提供一个可以运行命令的 shell。以下命令将在 pod 上启动一个 Bash 终端:

    kubectl exec -it <frontend-pod-name> -- bash
    

    这将进入一个 Bash shell 环境,如图 7.11所示:

    在 pod 中执行命令以启动并进入 Bash 终端

    图 7.11:获取 pod 名称并访问 pod 内的 shell

  2. 一旦进入容器 shell,运行以下命令:

    apt update
    apt install -y vim
    

    上面的代码安装了vim编辑器,以便我们可以编辑文件并引入错误。

  3. 现在,使用vim打开guestbook.php文件:

    vim guestbook.php
    
  4. 在第 17 行,在if ($_GET['cmd'] == 'set') {下添加以下代码。记住,在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.12所示:

    显示引入错误和附加日志记录的更新代码的输出

    图 7.12:引入错误和附加日志记录的更新代码

  5. 现在你已经引入了一个错误,阅读消息会正常工作,但无法写入消息。你通过让前端连接到不存在的本地主机 Redis 主服务器来实现这一点。写入操作应该会失败。同时,为了让这个演示更加直观,我们在这段代码中添加了一些额外的日志记录。

    打开你的访客留言应用,通过浏览其公共 IP 地址,你应该能看到之前的条目:

    留言板应用程序显示先前的条目

    图 7.13:先前的条目仍然存在

  6. 现在,通过输入一条消息并点击提交按钮来创建一条新消息:在留言板应用程序中创建新消息

    图 7.14:创建了新消息

    提交新消息后,它会出现在应用程序中。如果你不了解,可能会认为条目已经成功写入数据库。然而,如果你刷新浏览器,你会看到消息已经不在了。

  7. 为了验证消息没有写入数据库,请在浏览器中点击刷新按钮;你将只看到最初的条目,而新条目已经消失:

刷新网页以确认新消息没有保存

图 7.15:新消息已消失

作为应用程序开发人员或运维人员,你可能会收到这样的工单:新部署后,新条目未保存。请修复此问题。

使用日志识别根本原因

解决的第一步是获取日志。

  1. 现在退出前端 Pod 并获取此 Pod 的日志:

    exit
    kubectl logs <frontend-pod-name>
    

    注意:

    你可以在kubectl logs后添加-f标志以获得实时日志流,命令如下:kubectl logs <pod-name> -f。在实时调试会话中非常有用。

  2. 你将看到像图 7.16中显示的条目:显示新消息作为应用程序日志一部分的输出

    图 7.16:新消息作为应用程序日志的一部分显示出来

  3. 因此,你知道错误发生在写入数据库的代码的set部分。当你看到条目hostname at the beginning of 'set' command localhost时,你就知道错误发生在这一行和客户端开始之间,所以$host = 'localhost'的设置一定是引发错误的地方。这个错误并不像你想象的那样不常见,正如你刚才看到的,除非有特别的指示要求刷新浏览器,否则很容易通过 QA。这对开发者来说可能是完全没问题的,因为他们可能在本地机器上运行了 Redis 服务器。

现在你已经通过 Kubernetes 日志定位了问题的根本原因,让我们开始解决这个错误,并让应用程序恢复到健康状态。

解决问题

有两种方法可以修复你引入的这个错误:你可以进入 Pod 并修改代码,或者你可以请求 Kubernetes 给我们一个健康的新 Pod。我们不建议对 Pod 进行手动修改,因此在下一步中,你将使用第二种方法。让我们通过删除故障 Pod 来修复这个错误:

kubectl delete pod <podname>

由于有一个 ReplicaSet 控制着这些 Pod,你应该立即获得一个新的 Pod,该 Pod 从正确的镜像启动。尝试再次连接到留言板并验证消息是否能在浏览器刷新后仍然存在。

以下几点总结了本节内容,讲解了如何识别错误以及如何修复它:

  • 错误可能以多种形式出现。

  • 部署团队遇到的大多数错误都是配置问题。

  • 使用日志来识别根本原因。

  • 在容器上使用 kubectl exec 是一个有用的调试策略。

  • 请注意,广泛允许 kubectl exec 是一个严重的安全风险,因为它允许 Kubernetes 操作员直接在他们有权限访问的 pod 中执行命令。确保只有一部分操作员能够使用 kubectl exec 命令。你可以使用基于角色的访问控制来管理这种访问限制,正如你将在第八章、AKS 中的基于角色的访问控制中学到的那样。

  • 任何打印到 stdoutstderr 的内容都会出现在日志中(与应用程序/语言/日志框架无关)。

在本节中,你向留言本应用程序引入了一个应用程序错误,并利用 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,并将其连接到您的 Pod。您已经在 第三章 AKS 上的应用程序部署 中学习了 configmap。使用以下命令创建 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:1.19.6-alpine
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 将暂时不接收任何流量,但会继续运行。

  • sleep 命令(这样容器会继续运行)。

您可以使用以下命令创建该部署。您还可以部署第二个版本的 server 2,它与 server 1 类似:

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 显示应用程序返回来自服务器 1 的流量

图 7.17:我们的应用程序正在从服务器 1 返回流量

在接下来的测试中,你将使用一个名为 testWeb.sh 的小脚本,它已包含在本章的代码示例中,用于连接到你的网页 50 次,这样你可以监控服务器 1 和服务器 2 之间的流量分配。你需要先将该脚本设置为可执行,然后在部署完全健康时运行该脚本:

chmod +x testWeb.sh
./testWeb.sh <external-ip>

在健康运行期间,我们可以看到服务器 1 和服务器 2 几乎均匀地被访问,服务器 1 被访问了 24 次,服务器 2 被访问了 26 次:

显示应用健康,流量在服务器 1 和服务器 2 之间进行负载均衡的输出

图 7.18:当应用健康时,流量在服务器 1 和服务器 2 之间进行负载均衡

现在让我们继续进行操作,使服务器 1 的就绪探针失败。为此,你将使用 kubectl 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.19 所示:

首先执行一个命令停止将流量导向服务器 1。然后,通过 kubectl get pods -w,服务器 1 pod 的就绪属性从 1/1 变为 0/1

图 7.19:失败的就绪探针导致服务器 1 没有任何 READY 容器

这将不再将流量导向服务器 1 pod。让我们验证这一点:

./testWeb.sh <external-ip>

流量应该被重定向到服务器 2:

显示所有流量都被导向服务器 2 的输出

图 7.20:现在所有流量都由服务器 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.21 的输出:

恢复就绪探针后,流量重新进行负载均衡

图 7.21:恢复就绪探针后,流量再次进行负载均衡

失败的就绪探针会导致 Kubernetes 不再将流量发送到失败的 pod。你已经通过使示例应用中的就绪探针失败来验证了这一点。在下一节中,你将探索失败的存活探针的影响。

失败的存活探针会重启 pod

你也可以使用存活探针重复之前的过程。当存活探针失败时,Kubernetes 会重启该 pod。让我们尝试通过删除健康文件来做到这一点:

kubectl exec <server 2 pod name> -- \
  rm /usr/share/nginx/html/healthy.html

让我们看看这对 pod 产生了什么影响:

kubectl get pods -w

你应该会看到 pod 在几秒钟内重启:

显示失败的存活探针导致 pod 重启的输出

图 7.22:失败的存活探针将导致 pod 被重启

图 7.22 所示,pod 已成功重启,影响有限。你可以通过运行 describe 命令来检查 pod 中发生的情况:

kubectl describe pod <server2 pod name>

上述命令将给出类似图 7.23的输出:

更多关于 Pod 的详细信息,显示因存活性探针失败导致 Pod 被重启

图 7.23:更多关于 Pod 的详细信息,显示存活性探针如何失败

describe 命令中,你可以清楚地看到 Pod 未通过存活性探针测试。在三次失败后,容器被终止并重启。

这就结束了存活性探针和就绪性探针的实验。请记住,这两者对你的应用程序都很有用:就绪性探针可以用来临时停止流量进入 Pod,从而减少负载;而存活性探针则用于在 Pod 出现实际故障时重启 Pod。

让我们确保清理刚刚创建的部署:

kubectl delete deployment server1 server2
kubectl delete service web

存活性探针和就绪性探针对于确保只有健康的 Pod 接收流量非常有用。在下一节中,你将探索 Kubernetes 报告的不同指标,这些指标可以帮助你验证应用程序的状态。

Kubernetes 报告的指标

Kubernetes 报告多个指标。在本节中,你将首先使用一些 kubectl 命令来获取这些指标。之后,你将查看 Azure 容器监控,看看 Azure 如何帮助进行容器监控。

节点状态和消耗

你 Kubernetes 中的节点是运行你应用程序的服务器。Kubernetes 会将 Pods 调度到集群中的不同节点。你需要监控节点的状态,以确保节点本身健康,并且节点有足够的资源来运行新的应用程序。

运行以下命令以获取集群中节点的信息:

kubectl get nodes

上述命令列出了它们的名称、状态和年龄:

运行 kubectl get nodes 命令以获取集群中节点的信息

图 7.24:该集群中有两个节点

你可以通过传递 -o wide 选项获得更多信息:

kubectl get -o wide nodes

输出列出了底层的 OS-IMAGEINTERNAL-IP 以及其他有用信息,可以在图 7.25中查看:

将 -o wide 选项添加到命令中,以显示更多关于节点的详细信息

图 7.25:使用 -o wide 添加更多关于节点的详细信息

你可以使用以下命令找出哪些节点消耗的资源最多:

kubectl top nodes

它显示了节点的 CPU 和内存使用情况:

输出显示节点的 CPU 和内存利用率

图 7.26:节点的 CPU 和内存利用率

请注意,这是当时的实际消耗,而不是某个节点的请求数。要获取请求数,可以执行以下命令:

kubectl describe node <node name>

这将显示每个 Pod 的请求和限制,以及整个节点的累计值:

输出显示每个 Pod 的请求和限制,以及分配的资源总量

图 7.27:描述节点显示了请求和限制的详细信息

正如你在 图 7.27 中所见,describe node 命令输出了每个 pod 跨命名空间的请求和限制。这是集群运营商验证集群负载的好方法,适用于所有命名空间。

您现在知道可以在哪里找到有关节点利用率的信息。在下一节中,您将深入了解如何获取单个 pod 的相同指标。

Pod 消耗

Pod 从 AKS 集群消耗 CPU 和内存资源。请求和限制用于配置 pod 可以消耗的 CPU 和内存量。请求用于保留最小数量的 CPU 和内存,而限制则用于设置每个 pod 的最大 CPU 和内存量。

在本节中,您将学习如何使用 kubectl 获取有关 pod 的 CPU 和内存利用率的信息。

让我们从探索如何查看当前正在运行的 pod 的请求和限制开始:

  1. 对于此示例,您将使用在 kube-system 命名空间中运行的 pods。获取此命名空间中的所有 pods:

    kubectl get pods -n kube-system
    

    这应该显示类似于 图 7.28

    显示在 kube-system 命名空间中运行的 pods

    图 7.28:运行在 kube-system 命名空间中的 pods

  2. 让我们获取一个 coredns pod 的请求和限制。这可以通过使用 describe 命令完成:

    kubectl describe pod coredns-<pod id> -n kube-system
    

    describe 命令中,应该有一个类似于 图 7.29 的部分:

显示 CoreDNS Pod 的限制和请求

图 7.29:CoreDNS pod 的限制和请求

这显示了这个 pod 的内存限制为 170Mi,没有 CPU 限制,并且请求了 100 m CPU(即 0.1 CPU)和 70Mi 内存。这意味着如果该 pod 消耗超过 170 MiB 的内存,Kubernetes 将重新启动该 pod。Kubernetes 还为该 pod 保留了 0.1 CPU 核心和 70 MiB 内存。

请求和限制用于在集群中执行容量管理。您还可以获取 pod 的实际 CPU 和内存消耗。运行以下命令,您将得到所有命名空间中实际的 pod 消耗:

kubectl top pods --all-namespaces

这应该显示类似于 图 7.30 的输出:

运行命令以获取所有命名空间中实际 pod 的消耗

图 7.30:查看 pod 的 CPU 和内存消耗

使用 kubectl top 命令显示了在运行命令时的 CPU 和内存消耗。在这种情况下,您可以看到 coredns pods 使用了 3m CPU 和 10Mi 内存。

在本节中,你使用了 kubectl 命令来获取集群中节点和 pod 的资源使用情况。这是有用的信息,但仅限于特定时刻。下一节,你将使用 Azure 门户来获取有关集群和集群上应用程序的更详细信息。你将从探索AKS Diagnostics面板开始。

使用 AKS Diagnostics

当你在 AKS 中遇到问题时,开始探索的好地方是AKS Diagnostics面板。它为你提供了有助于调查与基础设施或系统集群组件相关的任何问题的工具。

注:

AKS Diagnostics 在本书编写时处于预览状态。这意味着功能可能会添加或删除。

要访问 AKS Diagnostics,请在 AKS 菜单中点击诊断并解决问题选项。这将打开 Diagnostics,如图 7.31所示:

在 Azure 门户中查看 AKS Diagnostics 面板概览

图 7.31:访问 AKS Diagnostics

AKS Diagnostics 提供了两个工具来诊断和探索问题,一个是Cluster Insights,另一个是Networking。Cluster Insights 利用集群日志和配置执行健康检查,并将集群与最佳实践进行对比。如果集群中有任何配置错误,它将包含有用的信息和相关的健康指标。Cluster Insights 的示例输出如图 7.32所示:

在 Azure 门户中看到的 Cluster Insights 示例输出

图 7.32:Cluster Insights 的示例输出

AKS Diagnostics 的 Networking 部分允许你交互式地排查集群中的网络问题。当你打开 Networking 视图时,会显示几个问题选项,选择其中一个选项后,将触发网络健康检查和配置审查。一旦选择了其中一个选项,交互式工具将给出这些检查的输出,如图 7.33所示:

使用 AKS Diagnostics 诊断网络问题

图 7.33:使用 AKS Diagnostics 诊断网络问题

当你在集群中遇到基础设施问题时,使用 AKS Diagnostics 非常有用。该工具会扫描你的环境,验证所有内容是否正常运行和配置良好。然而,它不会扫描你的应用程序。这时,Azure Monitor 就派上用场了;它允许你监控你的应用程序并访问应用程序日志。

Azure Monitor 指标和日志

在本章之前,你已经使用 kubectl 命令行工具查看了集群中节点和 pod 的状态及指标。在 Azure 中,你可以获取更多来自节点和 pod 的指标,并探索集群中 pod 的日志。现在,让我们从 Azure 门户开始探索 AKS Insights。

AKS Insights

AKS 面板的 Insights 部分提供了你需要了解的大部分集群指标。它还具有深入到容器级别的能力。你还可以查看容器的日志。

注意:

AKS 面板的 Insights 部分依赖于 Azure Monitor for containers。如果你使用门户默认

Kubernetes 提供了指标,但不会存储它们。Azure Monitor 可以用于存储这些指标,并使其可以随时间查询。为了收集相关的指标和日志并将其存储到 Insights 中,Azure 连接到 Kubernetes API 收集这些指标和日志,然后将其存储到 Azure Monitor。

注意:

容器的日志可能包含敏感信息。因此,查看日志的权限应当受到控制和审计。

让我们从 AKS 面板的 Insights 选项卡开始,首先查看集群指标。

集群指标

Insights 显示集群指标。图 7.34 显示了集群中所有节点的 CPU 和内存利用率。你还可以选择添加额外的过滤器来筛选特定的命名空间、节点或节点池。还有一个实时选项,可以让你获得集群状态的更多实时信息:

在 Cluster 选项卡中查看集群的 CPU 和内存利用率

图 7.34:Cluster 选项卡显示集群的 CPU 和内存利用率

集群指标还显示节点数量和活动 Pod 数量。节点数量很重要,因为你可以追踪是否有任何节点处于 Not Ready 状态:

检查 Cluster 选项卡中的节点数量和活动 Pod 数量

图 7.35:Cluster 选项卡显示节点数量和活动 Pod 数量

Cluster 选项卡可用于监控集群中节点的状态。接下来,你将探索 Reports 选项卡。

报告

AKS Insights 中的 Reports 选项卡让你访问多个预配置的监控工作簿。这些工作簿将文本、日志查询、指标和参数结合在一起,提供丰富的互动报告。你可以深入每个单独的报告以获取更多信息和预建的日志查询。可用的报告显示在 图 7.36 中:

注意

报告功能在本书写作时仍处于预览阶段。

Reports 选项卡概览,允许访问预配置的监控工作簿

图 7.36:Reports 选项卡让你访问预配置的监控工作簿

作为示例,你可以探索 Deployments 工作簿。它在 图 7.37 中显示:

Deployments 工作簿概览,显示所有部署的状态

图 7.37:Deployments 工作簿显示你的部署状态

默认情况下,这会显示所有部署的健康状况和最新状态。正如您所看到的,它显示了server1在您使用存活性和就绪性探针进行探索时曾暂时不可用。

您可以进一步深入查看单个部署的状态。如果您点击图 7.37中突出的日志按钮,将重定向到 Log Analytics,并展示一个预构建的查询。然后,您可以修改此查询并深入分析您的工作负载,如图 7.38所示。

在 Log Analytics 中深入查看以获取有关部署的更多详细信息

图 7.38:在 Log Analytics 中深入查看,获取更多关于部署的详细信息

注意:

在 Log Analytics 中使用的查询利用了Kusto 查询语言KQL)。要了解更多关于 KQL 的信息,请参考文档:docs.microsoft.com/azure/data-explorer/kusto/concepts/

AKS Insights 中的Reports标签页为您提供了多个预构建的监控工作簿。下一个标签页是节点标签页。

节点

节点视图为您展示了节点的详细指标。它还显示了每个节点上运行的 Pod,您可以在图 7.39中看到:

在节点面板中看到的节点详细指标

图 7.39:节点面板中的节点详细指标

请注意,您可以从搜索栏旁边的下拉菜单中查看不同的指标。如果需要更多详细信息,您还可以点击查看 Kubernetes 事件日志:

点击“查看 Kubernetes 事件日志”选项以获取集群日志

图 7.40:点击“查看 Kubernetes 事件日志”以获取集群日志

这将打开 Azure Log Analytics,并为您预先创建一个查询,显示您的节点日志。在图 7.41中,您可以看到该节点重启了几次,并且触发了InvalidDiskCapacity警告:

显示为节点预先创建查询并显示节点日志的 Log Analytics 概览

图 7.41:Log Analytics 显示节点日志

这将为您提供节点状态的信息。接下来,您将探索Controllers标签页。

控制器

Controllers标签页为您展示集群中所有控制器(即 ReplicaSets、DaemonSets 等)及其运行的 Pod。这为您提供了一个以控制器为中心的运行中的 Pod 视图。例如,您可以找到server1 ReplicaSet,并查看其中运行的所有 Pod 和容器,如图 7.42所示:

显示在 ReplicaSet 中运行的所有 Pod 的 Controllers 标签页概览

图 7.42:Controllers 标签页显示 ReplicaSet 中运行的所有 Pod

下一个标签页是容器标签页,它将显示容器的指标、日志和环境变量。

容器指标、日志和环境变量

点击容器标签页可以列出容器的指标、环境变量,并访问其日志,如图 7.43所示:

显示所有单独容器的容器标签页概览

图 7.43:容器标签页显示所有单独的容器

注意:

你可能会注意到一些容器处于Unknown状态。如果容器处于unknown状态,那是因为 Azure Monitor 已有该容器的日志和信息,但该容器已不再集群中运行。

你还可以通过此视图访问容器的日志:

点击“查看容器日志”选项,以访问容器标签页中的日志

图 7.44:访问容器的日志

这将显示 Kubernetes 从你的应用程序记录的所有日志。在本章早些时候,你使用了kubectl来访问容器日志。使用这种方法可能更高效,因为你可以编辑日志查询,并在一个视图中关联来自不同 Pod 和应用程序的日志:

显示 Kubernetes 从应用程序记录的日志的单一视图

图 7.45:日志被收集并可以查询

除了日志外,此视图还显示为容器设置的环境变量。要查看环境变量,请在容器视图的右侧单元格中向下滚动:

查看为容器设置的环境变量

图 7.46:为容器设置的环境变量

AKS Insights 中的最后一个标签页是部署标签页,接下来你将探索该标签页。

部署

最后一个标签页是部署标签页。此标签页提供集群中所有部署的概览,并允许你通过选择部署获取其定义。如图 7.47所示,你可以以描述(文本格式)或RAW(YAML 格式)查看此视图:

在 AKS Insights 面板中查看“部署”标签页概览

图 7.47:AKS Insights 中的“部署”标签页

使用 AKS 中的Insights面板,你可以获取有关集群的详细信息。在本节中,你探索了不同的标签页,并学习了如何深入挖掘并访问可定制的日志查询,以获取更多信息。

这部分内容到此结束。让我们确保通过以下命令清理本章中创建的所有资源:

kubectl delete -f 

在本节中,你探讨了如何监控在 Kubernetes 上运行的应用程序。你使用了 Azure 门户中的 AKS Insights 标签页,以详细查看集群及其上运行的容器。

小结

你从学习如何使用不同的kubectl命令来监控一个应用程序开始。然后,你探索了如何利用 Kubernetes 中创建的日志来调试该应用程序。这些日志包含了所有写入 stdoutstderr 的信息。

接下来,你切换到 Azure 门户并开始使用 AKS 诊断工具来探索基础设施问题。最后,你探索了使用 Azure Monitor 和 AKS Insights 来显示 AKS 的指标和环境变量,以及带有日志过滤的日志。

在下一个章节中,你将学习如何将 AKS 集群连接到 Azure PaaS 服务。你将特别关注如何将 AKS 集群连接到 Azure 管理的 MySQL 数据库。

第三部分:保护您的 AKS 集群和工作负载

Loose lips sink ships(“口无遮拦,害人害己”)这句话描述了如何容易地危及一个 Kubernetes 管理的集群的安全性(顺便提一下,Kubernetes 是希腊语中“舵手”的意思,就像的舵手一样)。如果您的集群暴露了错误的端口或服务,或者在应用程序定义中使用了明文的机密,坏人可以利用这种疏忽的安全措施,几乎可以为所欲为地操控您的集群。

在保护Azure Kubernetes ServiceAKS)集群及其上运行的工作负载时,需要考虑多个因素。在本节中,您将了解四种保护集群和应用程序的方法。您将了解 Kubernetes 中的基于角色的访问控制,并了解如何将其与Azure Active DirectoryAzure AD)集成。之后,您将学习如何允许 Pod 访问 Azure 资源,如 Blob 存储或 Key Vault,使用 Azure AD Pod 身份。接下来,您将了解 Kubernetes 机密以及如何将其与 Key Vault 安全地集成。最后,您将学习网络安全,并了解如何隔离您的 Kubernetes 集群。

在本章中,您将定期删除集群并创建启用新功能的新集群。如果您正在使用免费试用版,删除现有集群的原因是为了节省成本并优化免费试用体验。

本节包含以下章节:

  • 第八章AKS 中的基于角色的访问控制

  • 第九章在 AKS 中使用 Azure Active Directory 管理的 Pod 身份

  • 第十章在 AKS 中存储机密

  • 第十一章AKS 中的网络安全

您将从第八章AKS 中的基于角色的访问控制开始,在本章中,您将配置 Kubernetes 中的基于角色的访问控制并将其与 Azure AD 集成。

第九章:8. 在 AKS 中基于角色的访问控制

到目前为止,你一直在使用一种访问方式来访问 Azure Kubernetes Service (AKS),该方式允许你创建、读取、更新和删除集群中的所有对象。这对于测试和开发非常有效,但在生产集群中不推荐使用。在生产集群中,建议利用 Kubernetes 中的 基于角色的访问控制 (RBAC) 仅授予用户一组有限的权限。

本章你将深入探索 Kubernetes RBAC。你将首先了解 Kubernetes 中 RBAC 的概念。然后,你将配置 Kubernetes 中的 RBAC,并将其与 Azure Active Directory (Azure AD) 集成。

本章将涉及以下主题:

  • Kubernetes 中的 RBAC

  • 在 AKS 集群中启用 Azure AD 集成

  • 在 Azure AD 中创建用户和组

  • 配置 AKS 中的 RBAC

  • 验证用户的 RBAC

    注意

    要完成 RBAC 示例,你需要一个 Azure AD 实例,并拥有全局管理员权限。

让我们通过解释 RBAC 开始本章内容。

Kubernetes 中的 RBAC 解析

在生产系统中,你需要允许不同的用户访问不同级别的资源;这就是 RBAC。建立 RBAC 的好处不仅是作为防护措施,防止意外删除关键资源,还能作为一项重要的安全功能,限制集群的完全访问权限仅授予真正需要的角色。在启用 RBAC 的集群中,用户只能访问和修改他们有权限的资源。

到目前为止,使用 Cloud Shell 时,你一直以 root 身份操作,这使得你可以在集群中做任何事情。对于生产环境,root 权限是危险的,应该尽可能限制。使用 最小权限原则 (PoLP) 登录任何计算机系统是公认的最佳实践。这可以防止访问安全数据和通过删除关键资源造成的意外停机。大约 22% 到 29% 的数据丢失归因于人为错误。你不想成为这个统计数字的一部分。

Kubernetes 开发者意识到这是一个问题,并在 Kubernetes 中添加了 RBAC 以及服务角色的概念,以控制对集群的访问。Kubernetes RBAC 有三个重要概念:

  • 无权限,并且每个权限需要被明确指定。权限示例包括 getwatchlist。角色还包含这些权限授予的资源。资源可以是所有的 pods、deployments 等,或者是特定对象(例如 pod/mypod)。

  • 主体:主体可以是被分配角色的用户或服务账户。在与 Azure AD 集成的 AKS 集群中,这些主体可以是 Azure AD 用户或组。

  • RoleBinding:RoleBinding 将一个主体与某个命名空间中的角色关联,或者在 ClusterRoleBinding 的情况下,关联整个集群。

需要理解的一个重要概念是,在与 AKS 交互时,有两层 RBAC:Azure RBAC 和 Kubernetes RBAC,如图 8.1所示。Azure RBAC 处理赋予用户在 Azure 中进行更改的角色,例如创建、修改和删除集群。Kubernetes RBAC 处理对集群中资源的访问权限。这两者是独立的控制平面,但可以使用源自 Azure AD 的相同用户和组。

两个 RBAC 平面的示意图,Azure 和 Kubernetes

图 8.1:两个不同的 RBAC 平面,Azure 和 Kubernetes

RBAC 在 Kubernetes 中是一个可选功能。AKS 的默认设置是创建启用 RBAC 的集群。然而,默认情况下,集群并未与 Azure AD 集成。这意味着,默认情况下你无法将 Kubernetes 权限授予 Azure AD 用户。在接下来的部分中,你将启用 Azure AD 集成到你的集群中。

启用 Azure AD 集成到你的 AKS 集群中

在本节中,你将更新现有集群,以包括 Azure AD 集成。你将使用 Azure 门户来完成此操作:

注意

一旦集群与 Azure AD 集成,此功能将无法禁用。

  1. 首先,你需要一个 Azure AD 组。稍后,你将为这个组授予 AKS 集群的管理员权限。要创建此组,在 Azure 搜索栏中搜索 azure active directory在 Azure 搜索栏中搜索 Azure Active Directory

    图 8.2:在 Azure 搜索栏中搜索 Azure Active Directory

  2. 在左侧面板中,选择“组”,这将带你进入“所有组”屏幕。点击 + 新建组,如图 8.3所示:在组面板中,点击 + 新建组按钮以创建新的 Azure AD 组

    图 8.3:创建一个新的 Azure AD 组

  3. 在随后的页面中,创建一个安全组并为其命名和描述。选择你的用户为该组的所有者和成员。点击屏幕上的创建按钮:提供必要的信息来创建 Azure AD 组

    图 8.4:为创建 Azure AD 组提供详细信息

  4. 现在,创建了这个组,在 Azure 搜索栏中搜索你的 Azure 集群,打开 AKS 面板:使用 Azure 搜索栏搜索集群

    图 8.5:在 Azure 搜索栏中搜索你的集群

  5. 在 AKS 面板中,在设置下选择集群配置。在此面板中,你可以启用 AKS 管理的 Azure Active Directory。启用此功能并选择你之前创建的 Azure AD 组,作为管理员 Azure AD 组。最后,点击命令栏中的保存按钮,如图 8.6所示:

启用 AKS 管理的 Azure Active Directory 并点击保存按钮

图 8.6:启用 AKS 管理的 Azure Active Directory 并点击保存按钮

这将在你的 AKS 集群上启用与 Azure AD 集成的 RBAC。在下一节中,你将创建一个新用户和新组,并将在接下来的部分中使用它们来设置和测试 Kubernetes 中的 RBAC。

在 Azure AD 中创建用户和组

在这一部分,你将创建一个新的用户和一个新的组,并将在本章后面使用它们来为你的 AKS 集群分配权限:

注意

你需要在 Azure AD 中具有 用户管理员角色,才能创建用户和组。

  1. 首先,在 Azure 搜索栏中搜索 azure active directory在 Azure 搜索栏中搜索 Azure Active Directory

    图 8.7:在搜索栏中搜索 Azure Active Directory

  2. 点击左侧面板中的所有用户,然后选择 + 新建用户以创建一个新用户:点击 + 新建用户按钮创建新用户

    图 8.8:点击 + 新建用户以创建一个新用户

  3. 提供有关用户的信息,包括用户名。确保记下密码,因为登录时需要用到:提供创建新用户所需的详细信息

    图 8.9:提供用户详细信息

  4. 用户创建后,返回 Azure AD 面板并选择组。然后点击 + 新建组按钮以创建一个新组:点击 + 新建组按钮创建新组

    图 8.10:点击 + 新建组以创建一个新组

  5. 创建一个新的安全组。将该组命名为 handson aks users,并将 Tim 添加为该组的成员。然后点击底部的创建按钮:提供创建新组所需的详细信息

    图 8.11:提供组类型、组名称和组描述

  6. 现在,你已经创建了一个新用户和一个新组。接下来,你将把该用户设为 AKS RBAC 中的集群用户。这使得他们可以使用 Azure CLI 来访问集群。为此,搜索你的集群名称并在 Azure 搜索栏中查找:使用 Azure 搜索栏搜索集群

    图 8.12:在 Azure 搜索栏中搜索你的集群

  7. 在集群面板中,点击访问控制(IAM),然后点击 + 添加按钮来添加一个新的角色分配。选择 Azure Kubernetes Service Cluster User 角色,并将其分配给你刚刚创建的用户:将集群用户角色分配给我们刚创建的新用户

    图 8.13:将集群用户角色分配给你创建的新用户

  8. 由于你还将使用 Cloud Shell 和新用户,因此你需要为他们提供对 Cloud Shell 存储账户的贡献者访问权限。首先,在 Azure 搜索栏中搜索 storage使用 Azure 搜索栏搜索存储账户

    图 8.14:在 Azure 搜索栏中搜索存储

  9. 在资源组下应该有一个存储账户,名称以 cloud-shell-storage 开头。点击资源组:选择存储账户的资源组

    图 8.15:选择资源组

  10. 转到访问控制(IAM),点击 + 添加按钮。为新创建的用户授予存储账户贡献者角色:

将贡献者角色分配给新创建的用户存储账户

图 8.16:将存储账户贡献者角色分配给新用户

这已经完成了新用户和组的创建,并为该用户授予了访问 AKS 的权限。在下一部分,您将为该用户和组配置 AKS 中的 RBAC。

在 AKS 中配置 RBAC

为了演示 AKS 中的 RBAC,您将创建两个命名空间并在每个命名空间中部署 Azure 投票应用。您将授予该组集群范围的只读访问权限,并授予用户仅在一个命名空间中删除 Pods 的权限。实际上,您需要在 Kubernetes 中创建以下对象:

  • ClusterRole 用于授予只读访问权限

  • ClusterRoleBinding 用于授予该组访问此角色的权限

  • Role 用于在 delete-access 命名空间中授予删除权限

  • RoleBinding 用于授予用户访问此角色的权限

展示您将构建的演示流程图。一个用户是某个组的一部分。该组有一个集群角色来读取 pods。该用户有一个附加的命名空间作用域角色,允许他们删除 pods

图 8.17:该组获得整个集群的只读访问权限,而用户则获得 delete-access 命名空间的删除权限

让我们在集群上设置不同的角色:

  1. 要开始我们的示例,您需要获取该组的 ID。以下命令将检索组 ID:

    az ad group show -g 'handson aks users' \
      --query objectId -o tsv
    

    这将显示您的组 ID。请记下它,因为您将在下一步中用到:

    显示组 ID 的输出

    图 8.18:获取组 ID

  2. 在 Kubernetes 中,您将为此示例创建两个命名空间:

    kubectl create ns no-access
    kubectl create ns delete-access
    
  3. 您还将在两个命名空间中部署 azure-vote 应用:

    kubectl create -f azure-vote.yaml -n no-access
    kubectl create -f azure-vote.yaml -n delete-access
    
  4. 接下来,您将创建 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"]
    

    让我们更仔细地查看这个文件:

    • ClusterRole 实例

    • ClusterRole 实例

    • getwatchlist

    我们将使用以下命令创建 ClusterRole

    kubectl create -f clusterRole.yaml
    
  5. 下一步是创建集群角色绑定。绑定将角色与用户或组关联。此操作在 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>"
    

    让我们更仔细地查看这个文件:

    • ClusterRoleBinding 实例。

    • ClusterRoleBinding

    • 我们在前一步创建的 ClusterRole 对象

    • 用您之前获取的组 ID 替换 第 12 行 中的 <group-id>

    我们可以使用以下命令创建 ClusterRoleBinding

    kubectl create -f clusterRoleBinding.yaml
    
  6. 接下来,您将创建一个仅限于 delete-access 命名空间的角色。此操作在 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 对象。它有两个重要的区别:

    • Role 实例,而不是 ClusterRole 实例

    • 使用以下命令创建 Role

      kubectl create -f role.yaml
      
    • 最后,你将创建一个 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 对象。它有几个重要的区别:

      • 创建 RoleBinding 实例,而不是 ClusterRoleBinding 实例

      • 创建 RoleBinding 实例

      • ClusterRole 实例

      • 使用以下命令创建 RoleBinding

        kubectl create -f roleBinding.yaml
        

这已经完成了 RBAC 的要求。你已经创建了两个角色——ClusterRole 和一个命名空间绑定角色,并设置了两个 RoleBindings 对象——ClusterRoleBinding 和命名空间绑定的 RoleBinding。在下一部分,你将通过以新用户身份登录集群来探索 RBAC 的影响。

验证用户的 RBAC

为了验证 RBAC 是否按预期工作,你将使用新创建的用户登录到 Azure 门户。打开 https://portal.azure.com 的新浏览器窗口或 InPrivate 窗口,并使用新创建的用户登录。系统会立即提示你更改密码。这是 Azure AD 的一项安全功能,确保只有该用户知道他们的密码:

提示用户更新密码

图 8.19:系统会提示你更改密码

一旦你更改了密码,就可以开始测试不同的 RBAC 角色:

  1. 你将通过为新用户设置 Cloud Shell 来开始此实验。启动 Cloud Shell 并选择 Bash:选择 Bash 作为 Cloud Shell

    图 8.20:在 Cloud Shell 中选择 Bash

  2. 在下一个对话框中,选择显示高级设置:点击显示高级设置按钮

    图 8.21:选择显示高级设置

  3. 然后,将 Cloud Shell 指向现有的存储帐户并创建一个新的文件共享:将 Cloud Shell 指向现有存储帐户并创建新文件共享

    图 8.22:指向现有存储帐户并创建新文件共享

  4. 一旦 Cloud Shell 可用,获取连接到 AKS 集群的凭据:

    az aks get-credentials -n handsonaks -g rg-handsonaks
    

    然后,在 kubectl 中尝试一个命令。我们来尝试获取集群中的节点:

    kubectl get nodes
    

    由于这是针对启用了 RBAC 的集群执行的第一个命令,你需要重新登录。浏览至 https://microsoft.com/devicelogin,并提供 Cloud Shell 显示给你的代码(这个代码在 图 8.24 中高亮显示)。确保使用新用户的凭据登录:

    输入 Cloud Shell 在提示中显示给我们的代码

    图 8.23:复制并粘贴 Cloud Shell 在提示中显示给你的代码

    登录后,您应该会收到来自kubectlForbidden错误消息,通知您没有权限查看集群中的节点。由于该用户仅配置了查看 pods 的权限,因此这是预期的:

    来自 kubectl 的 Forbidden 错误消息

    图 8.24:提示您登录的消息以及 Forbidden 错误

  5. 现在,您可以验证您的用户是否具有查看所有命名空间中 pods 的权限,并且该用户是否有权限删除delete-access命名空间中的 pods:

    kubectl get pods -n no-access
    kubectl get pods -n delete-access
    

    这应该在两个命名空间中都成功。原因是为用户组配置的ClusterRole对象:

    确认可以查看两个命名空间中 pods 的输出

    图 8.25:用户可以查看两个命名空间中的 pods

  6. 让我们也验证一下delete权限:

    kubectl delete pod --all -n no-access
    kubectl delete pod --all -n delete-access
    

    正如预期的那样,在no-access命名空间中被拒绝访问,而在delete-access命名空间中允许访问,如图 8.26所示:

验证用户无法删除 no-access 命名空间中的资源,但可以删除 delete-access 命名空间中的资源的输出

图 8.26:在 no-access 命名空间中删除操作被拒绝,在 delete-access 命名空间中允许删除操作

在这一部分,您已验证了 Kubernetes 集群中 RBAC 的功能。由于这是本章的最后一部分,我们需要确保清理集群中的部署和命名空间。请确保使用您的主用户从 Cloud Shell 执行这些步骤,而不是新用户:

kubectl delete -f azure-vote.yaml -n no-access
kubectl delete -f azure-vote.yaml -n delete-access
kubectl delete -f .
kubectl delete ns no-access
kubectl delete ns delete-access

本章结束了 AKS 上 RBAC 的概述。

摘要

在本章中,您了解了 AKS 上的 RBAC。您在集群中启用了 Azure AD 集成的 RBAC。之后,您创建了一个新用户和组,并在集群上设置了不同的 RBAC 角色。最后,您使用该用户登录并验证了配置的 RBAC 角色是否限制了您对集群的访问。

这一部分介绍了用户如何获取对 Kubernetes 集群的访问权限。运行在集群上的 pods 可能还需要在 Azure AD 中有一个身份,供它们访问 Azure 服务中的资源,如 Blob 存储或密钥保管库。在下一章中,您将了解更多关于此用例的信息,以及如何在 AKS 中使用 Azure AD pod 身份来设置此功能。

第十章:9. AKS 中的 Azure Active Directory Pod 托管身份

在上一章中,第八章,AKS 中的基于角色的访问控制,您将 AKS 集群与Azure Active DirectoryAzure AD)进行了集成。然后,您将 Kubernetes 角色分配给 Azure AD 中的用户和组。在本章中,您将探讨如何将运行在 AKS 上的应用程序与 Azure AD 集成,并学习如何为您的 Pod 在 Azure 中分配一个身份,使其能够与其他 Azure 资源交互。

在 Azure 中,应用程序身份使用一种称为服务主体的功能。服务主体是云中服务帐户的等价物。应用程序可以使用服务主体进行身份验证,以便访问 Azure AD 并获取资源。这些资源可以是 Azure 资源,例如 Azure Blob 存储或 Azure 密钥库,或者是您开发的与 Azure AD 集成的应用程序。

有两种方式可以对服务主体进行身份验证:您可以使用密码或证书与私钥的组合。尽管这些是安全的身份验证方式,但管理密码或证书及其轮换可能会很繁琐。

Azure 中的托管身份是一项使身份验证服务主体变得更加简便的功能。它通过为 Azure 中的计算资源(如虚拟机或 Azure 函数)分配一个身份来实现。那些计算资源可以通过调用仅该机器能够访问的端点来使用该托管身份进行身份验证。这是一种安全的身份验证方式,不需要您管理密码或证书。

Azure AD Pod 托管身份允许您为 Kubernetes 中的 Pod 分配托管身份。由于 Kubernetes 中的 Pod 运行在虚拟机上,默认情况下,每个 Pod 都能够访问托管身份端点,并使用该身份进行身份验证。使用 Azure AD Pod 托管身份后,Pod 不再能访问虚拟机的内部端点,而只能访问分配给特定 Pod 的身份。

在本章中,您将为 AKS 集群配置 Azure AD Pod 托管身份,并使用它访问 Azure Blob 存储。在下一章中,您将使用这些 Azure AD Pod 托管身份访问 Azure Key Vault 并管理 Kubernetes 密钥。

本章将简要介绍以下主题:

  • Azure AD Pod 托管身份概述

  • 使用 Azure AD Pod 托管身份设置新集群

  • 将身份链接到您的集群

  • 使用托管身份的 Pod

让我们从 Azure AD Pod 托管身份的概述开始。

Azure AD Pod 托管身份概述

本节的目标是描述 Azure 托管身份和 Azure AD Pod 托管身份。

如介绍中所述,Azure 中的托管身份是一种用于安全地验证在 Azure 内部运行的应用程序的方法。Azure 中有两种托管身份类型。它们之间的区别在于如何与资源关联:

  • 系统分配:这种类型的托管身份与资源(例如虚拟机)本身是 1:1 关联的。这个托管身份还共享资源的生命周期,意味着一旦资源被删除,托管身份也会被删除。

  • 用户分配:用户分配的托管身份是独立的 Azure 资源。用户分配的托管身份可以与多个资源关联。当一个资源被删除时,托管身份不会被删除。

一旦托管身份被创建并与资源关联,两种类型的托管身份的工作方式是相同的。以下是从应用程序的角度来看托管身份的工作原理:

  1. 运行在 Azure 中的应用程序请求一个令牌到 169.254.169.254

  2. IMDS 会向 Azure AD 请求一个令牌。它使用为你的托管身份配置的证书,且该证书仅由 IMDS 知道。

  3. Azure AD 会将令牌返回给 IMDS,IMDS 再将该令牌返回给你的应用程序。

  4. 你的应用程序可以使用此令牌对其他资源进行身份验证,例如 Azure Blob 存储。

Azure 虚拟机中的托管身份工作流

图 9.1:Azure 虚拟机中的托管身份

在 Kubernetes 集群中在单个虚拟机上运行多个 Pod 时,默认情况下每个 Pod 都可以访问 IMDS 端点。这意味着每个 Pod 都可能访问该虚拟机配置的身份。

AKS 的 Azure AD Pod 托管身份附加组件会以一种方式配置你的集群,使得 Pod 无法直接访问 IMDS 端点来请求访问令牌。它将集群配置为当 Pod 尝试访问 IMDS 端点时(1),会连接到集群上运行的一个 DaemonSet。这个 DaemonSet 被称为 节点托管身份NMI)。NMI 将验证该 Pod 应该访问哪些身份。如果该 Pod 被配置为可以访问请求的身份,那么 DaemonSet 会连接到 IMDS(2 到 5)以获取令牌,然后将令牌传递给 Pod(6)。然后,Pod 可以使用这个令牌访问 Azure 资源(7)。

Azure AD Pod 托管身份工作流

图 9.2:Azure AD Pod 托管身份

通过这种方式,你可以控制集群中的哪些 Pod 可以访问某些身份。

Azure AD Pod 托管身份最初是由微软在 GitHub 上作为开源项目开发的。最近,微软将 Azure AD Pod 托管身份发布为 AKS 附加组件。使用 Azure AD Pod 托管身份作为 AKS 附加组件的好处是该功能由微软支持,软件将作为常规集群操作的一部分自动更新。

注意

截至撰写时,Azure AD Pod 管理的身份插件处于预览阶段。目前,它也不支持 Windows 容器。在产品用例中使用预览功能并不推荐。

现在你已经了解了 Azure AD Pod 管理身份的工作原理,让我们在下一节中在 AKS 集群上进行设置。

使用 Azure AD Pod 管理的身份设置新集群

如前一节所述,有两种方式可以在 AKS 中设置 Azure AD Pod 管理的身份。可以使用 GitHub 上的开源项目,或者将其设置为 AKS 插件。通过使用插件,你将获得受支持的配置,因此在本节中你将使用插件设置集群。

截至撰写时,尚无法在现有集群上启用 Azure AD Pod 管理的身份插件,因此在以下的操作中,你将删除现有集群并创建一个安装了插件的新集群。等你阅读此内容时,可能已经可以在现有集群上启用此插件,而无需重新创建集群。

另外,由于该功能在撰写时处于预览阶段,你需要注册预览功能。这将是本节的第一步:

  1. 首先,打开 Cloud Shell 并注册 Azure AD Pod 管理的身份预览功能:

    az feature register --name EnablePodIdentityPreview \
      --namespace Microsoft.ContainerService
    
  2. 你还需要一个 Azure CLI 的预览扩展,你可以使用以下命令来安装:

    az extension add --name aks-preview
    
  3. 现在你可以继续删除现有集群。这是为了确保你在 Azure 中有足够的核心配额。你可以使用以下命令来执行此操作:

    az aks delete -n handsonaks -g rg-handsonaks --yes
    
  4. 一旦你的旧集群被删除,你需要等待直到 Pod 身份预览功能在你的订阅中注册。你可以使用以下命令来验证此状态:

    az feature show --name EnablePodIdentityPreview \
      --namespace Microsoft.ContainerService -o table
    

    等待直到状态显示为已注册,如图 9.3所示:

    验证 Pod 身份预览功能是否已在你的订阅中注册

    图 9.3:等待功能注册

  5. 如果功能已注册并且旧集群已删除,你需要在创建新集群之前刷新命名空间的注册。让我们首先刷新命名空间的注册:

    az provider register --namespace Microsoft.ContainerService
    
  6. 现在,你可以使用 Azure AD Pod 管理的身份插件创建一个新的集群。你可以使用以下命令创建一个启用了插件的新集群:

    az aks create -g rg-handsonaks -n handsonaks \
      --enable-managed-identity --enable-pod-identity \
      --network-plugin azure --node-vm-size Standard_DS2_v2 \
      --node-count 2 --generate-ssh-keys
    
  7. 这将花费几分钟时间来完成。命令执行完毕后,获取凭据以访问你的集群,并使用以下命令验证你可以访问集群:

    az aks get-credentials -g rg-handsonaks \
      -n handsonaks --overwrite-existing
    kubectl get nodes
    

    这应该返回类似于图 9.4的输出:

获取访问新集群的凭据并验证你可以访问新集群

图 9.4:获取集群凭据并验证访问

现在你已经有了一个启用了 Azure AD pod 管理身份的新的 AKS 集群。在接下来的章节中,你将创建一个管理身份并将其与集群关联。

将身份链接到你的集群

在上一节中,你创建了一个启用了 Azure AD pod 管理身份的新集群。现在你已经准备好创建一个管理身份并将其与集群关联。让我们开始吧:

  1. 首先,你需要通过 Azure 门户创建一个新的管理身份。在 Azure 门户中,在搜索框中搜索managed identity,如图 9.5所示:在 Azure 搜索框中搜索 Managed identity

    图 9.5:在 Azure 门户中导航到 Managed Identities

  2. 在结果面板中,点击顶部的+新建按钮。为了将本章的资源整理在一起,建议创建一个新的资源组。在结果面板中,点击“创建新建”按钮以创建一个新的资源组。将其命名为aad-pod-id,如图 9.6所示:创建名为 aad-pod-id 的新资源组

    图 9.6:创建新的资源组

  3. 现在,选择你创建集群时使用的区域作为管理身份的区域,并为它命名(在本例中为aad-pod-id),如图 9.7所示。最后,点击“查看 + 创建”按钮,在最后一个窗口中点击“创建”按钮来创建你的管理身份:为创建新的管理身份提供实例详细信息

    图 9.7:为管理身份提供实例详细信息

  4. 一旦管理身份创建完成,点击“转到资源”按钮进入资源页面。在这里,你需要复制客户端 ID 和资源 ID。它们将在本章稍后使用。将这些值复制并粘贴到一个方便以后访问的位置。首先,你需要管理身份的客户端 ID。你可以在管理身份的概览面板中找到它,如图 9.8所示:从管理身份概览页面获取客户端 ID

    图 9.8:获取管理身份的客户端 ID

  5. 最后,你还需要管理身份的资源 ID。你可以在管理身份的属性面板中找到它,如图 9.9所示:从管理身份属性页面获取资源 ID

    图 9.9:获取管理身份的资源 ID

  6. 现在你已经准备好将管理身份链接到你的 AKS 集群。为此,你需要在 Cloud Shell 中运行一个命令,之后你将能够验证该身份是否已在你的集群中可用。我们先从链接身份开始。确保将 <Managed identity resource ID> 替换为你之前复制的资源 ID:

    az aks pod-identity add --resource-group rg-handsonaks \
      --cluster-name handsonaks --namespace default \
      --name access-blob-id \
      --identity-resource-id <Managed identity resource ID>
    
  7. 你可以通过运行以下命令来验证你的身份是否成功地链接到集群:

    kubectl get azureidentity
    

    这应该会给你类似于图 9.10的输出:

验证身份是否成功链接到集群

图 9.10:验证身份在集群中的可用性

这意味着该身份现在可以在你的集群中使用。如何操作将在下一节中说明。

使用托管身份的 Pod

在上一节中,你创建了一个托管身份并将其链接到你的集群。在本节中,你将创建一个新的 Blob 存储帐户,并赋予你创建的托管身份对该存储帐户的权限。然后,你将在集群中创建一个新 Pod,该 Pod 可以使用该托管身份与存储帐户进行交互。让我们从创建一个新的存储帐户开始:

  1. 要创建新的存储帐户,搜索 Azure 搜索栏中的 storage accounts,如 图 9.11 所示:在 Azure 搜索栏中查找存储帐户

    图 9.11:在 Azure 搜索栏中查找存储帐户

    在弹出的窗格中,点击屏幕顶部的“+ 新建”按钮,如 图 9.12 所示:

    创建新的存储帐户

    图 9.12:创建新的存储帐户

    选择你之前创建的 aad-pod-id 资源组,给帐户指定一个唯一的名称,并选择与你的集群相同的区域。为了优化成本,建议选择标准性能、StorageV2 作为帐户类型,并选择本地冗余存储(LRS)作为复制选项,如 图 9.13 所示:

    通过提供订阅和实例详细信息配置新的存储帐户

    图 9.13:配置新的存储帐户

  2. 在提供所有值后,点击“查看 + 创建”,然后在弹出的屏幕上点击“创建”按钮。这将需要大约一分钟的时间来完成创建。存储帐户创建完成后,点击“转到资源”按钮继续下一步。

  3. 首先,你需要赋予托管身份访问存储帐户的权限。为此,在左侧导航栏中点击“访问控制(IAM)”,然后点击“+ 添加”并选择“添加角色分配”。接下来,选择“Storage Blob Data Contributor”角色,在“分配访问权限给”下拉菜单中选择“用户分配的托管身份”,然后选择你创建的 access-blob-id 托管身份,如 图 9.14 所示。最后,点击屏幕底部的“保存”按钮:在存储帐户的 IAM 窗格中为托管身份提供访问权限

    图 9.14:为托管身份提供存储帐户访问权限

  4. 接下来,你将上传一个随机文件到该存储帐户。稍后,你将尝试从 Kubernetes Pod 中访问该文件,以验证你是否有权限访问存储帐户。为此,返回存储帐户的“概览”窗格。在那里,点击“容器”,如 图 9.15 所示:从存储帐户的概览窗格导航到容器

    图 9.15:在概述窗格中单击容器

  5. 然后,在屏幕顶部点击“+ 容器”按钮。给容器命名,例如uploadedfiles。确保将公共访问级别设置为私有(无匿名访问),然后在屏幕底部点击“创建”按钮,如图 9.16所示:创建名为 uploadedfiles 的新容器

    图 9.16:创建一个新的 Blob 存储容器

  6. 最后,将一个随机文件上传到此存储容器中。要做到这一点,请单击容器名称,然后在屏幕顶部单击“上传”按钮。从您的计算机中选择一个随机文件,然后单击“上传”,如图 9.17所示:将新文件上传到 Blob 存储

    图 9.17:将新文件上传到 blob 存储

  7. 现在您已经在 blob 存储中有一个文件,并且您的托管身份可以访问此存储帐户,您可以继续尝试从 Kubernetes 连接到它。为此,您将使用 Azure CLI 容器映像创建一个新的部署。此部署将包含链接到先前创建的托管身份的链接。本章的代码文件中提供了名为 deployment-with-identity.yaml 的部署文件:

    1   apiVersion: apps/v1
    2   kind: Deployment
    3   metadata:
    4     name: access-blob
    5   spec:
    6     selector:
    7       matchLabels:
    8         app: access-blob
    9     template:
    10      metadata:
    11        labels:
    12          app: access-blob
    13          aadpodidbinding: access-blob-id
    14      spec:
    15        containers:
    16        - name: azure-cli
    17          image: mcr.microsoft.com/azure-cli
    18          command: [ "/bin/bash", "-c", "sleep inf" ]
    

    在定义此部署时需要注意以下几点:

    • mcr.microsoft.com/azure-cli) 指的是 Azure CLI,您正在该容器中运行 sleep 命令,以确保容器不会持续重新启动。
  8. 您可以使用以下命令创建此部署:

    kubectl create -f deployment-with-identity.yaml
    
  9. 观察 pod 直到 access-blob pod 处于 exec 状态,并使用以下命令进入其中:

    kubectl exec -it <access-blob pod name> -- sh
    
  10. 一旦连接到 pod,您可以使用以下命令对 Azure API 进行身份验证。用先前复制的客户端 ID 替换 <托管身份的客户端 ID>

    az login --identity -u <client ID of managed identity> \
      --allow-no-subscription -o table
    

    这应该会返回类似 图 9.18 的输出:

    使用托管身份的客户端 ID 登录 Azure CLI

    图 9.18:使用 Azure AD 管理身份登录 Azure CLI

  11. 现在,您可以尝试访问 blob 存储帐户并下载文件。您可以执行以下命令来完成这一操作:

    az storage blob download --account-name <storage account name> \
     --container-name <container name> --auth-mode login \
     --file <filename> --name <filename> -o table
    

    这应该会返回类似 图 9.19 的输出:

    使用托管身份下载 blob 文件

    图 9.19:使用托管身份下载 blob 文件

  12. 您现在可以使用 exit 命令退出容器。

  13. 如果您希望验证未配置托管身份且无法下载文件的 pod,您可以使用名为 deployment-without-identity.yaml 的文件:

    1   apiVersion: apps/v1
    2   kind: Deployment
    3   metadata:
    4     name: no-access-blob
    5   spec:
    6     selector:
    7       matchLabels:
    8         app: no-access-blob
    9     template:
    10      metadata:
    11        labels:
    12          app: no-access-blob
    13      spec:
    14        containers:
    15        - name: azure-cli
    16          image: mcr.microsoft.com/azure-cli
    17          command: [ "/bin/bash", "-c", "sleep inf" ]
    

    正如您所看到的,此部署与本章早期创建的部署不同。这里的区别在于 pod 定义不包含带有 Azure AD pod-managed identity 的标签。这意味着该 pod 将无法使用任何托管身份登录 Azure。您可以使用以下命令创建此部署:

    kubectl create -f deployment-without-identity.yaml
    
  14. 观察 pod,直到 no-access-blob pod 进入 access-blob pod,并使用以下命令 exec 进入它:

    kubectl exec -it <no-access-blob pod name> -- sh
    
  15. 一旦连接到 pod,你可以尝试使用以下命令通过 Azure API 进行身份验证,应该会失败:

    az login --identity -u <client ID of managed identity> \
      --allow-no-subscription -o table
    

    这应返回类似于图 9.20的输出:

    带有 pod 身份的部署的身份验证错误

    图 9.20:新 pod 无法使用托管身份进行身份验证

  16. 最后,你可以使用exit命令退出容器。

这成功地展示了如何使用 Azure AD pod 管理的身份在 Kubernetes 集群内连接到 Blob 存储。带有身份标签的部署可以登录 Azure CLI 并访问 Blob 存储,而没有此身份标签的部署则无法登录 Azure CLI,因此也无法访问 Blob 存储。

本章内容已结束。让我们确保删除你为本章创建的资源:

az aks pod-identity delete --resource-group rg-handsonaks \
  --cluster-name handsonaks --namespace default \
  --name access-blob-id 
az group delete -n aad-pod-id --yes
kubectl delete -f

你可以保留本章创建的集群,因为在下一章你将使用 Azure AD pod 管理的身份来访问 Key Vault 秘密。

摘要

在本章中,你继续探索了 AKS 中的安全性。第八章,AKS 中的基于角色的访问控制 关注的是用户身份,而本章则聚焦于在 pod 中运行的 pod 和应用程序的身份。你学习了 Azure 中的托管身份,并了解了如何使用 Azure AD pod 管理的身份将这些托管身份分配给 pod。

你创建了一个启用了 Azure AD pod 管理的身份附加组件的新集群。然后,你创建了一个新的托管身份,并将其与集群关联。在最后的部分,你为该身份授予了 Blob 存储帐户的权限,并最终验证了带有托管身份的 pod 能够登录 Azure 并下载文件,但没有托管身份的 pod 无法登录 Azure。

在下一章,你将深入了解 Kubernetes 秘密。你将了解内置的秘密,并学习如何将 Kubernetes 安全地连接到 Azure Key Vault,甚至使用 Azure AD pod 管理的身份来实现这一点。

第十一章:10. 在 AKS 中存储密钥

所有生产应用程序都需要一些敏感信息来正常运行,比如密码或连接字符串。Kubernetes 提供了一个可插拔的后端来管理这些密钥。Kubernetes 还提供了多种方法来在部署中使用这些密钥。能够管理密钥并正确使用它们将使你的应用程序更加安全。

你已经在本书中之前使用过密钥。当你在 第三章、在 AKS 上部署应用程序第四章、构建可扩展应用程序 中连接 WordPress 网站并创建博客文章时,你使用了密钥。你还在 第六章、使用 HTTPS 保护你的应用程序 中使用了密钥,当时你正在配置带有 TLS 的应用程序网关入口控制器。

Kubernetes 拥有一个内置的密钥系统,它将密钥以半加密方式存储在默认的 Kubernetes 数据库中。这个系统运行良好,但并不是在 Kubernetes 中处理密钥的最安全方法。在 AKS 中,你可以利用一个名为 Azure 密钥保管库提供程序的密钥存储 CSI 驱动程序CSI 驱动程序)的项目,它是一种更安全的处理 Kubernetes 密钥的方式。该项目允许你在 Azure 密钥保管库中存储和检索密钥。

在本章中,你将了解 Kubernetes 中的各种内建密钥类型以及你可以创建这些密钥的不同方式。之后,你将安装 CSI 驱动程序到集群中,并使用它来检索密钥。

本章将涵盖以下主题:

  • Kubernetes 中不同类型的密钥

  • 在 Kubernetes 中创建和使用密钥

  • 安装 Azure 密钥保管库提供程序的密钥存储 CSI 驱动程序

  • 使用 Azure 密钥保管库提供程序进行密钥存储 CSI 驱动程序

让我们从探索 Kubernetes 中的不同类型的密钥开始。

Kubernetes 中不同类型的密钥

如本章介绍中所提到的,Kubernetes 自带默认的密钥实现。该默认实现将密钥存储在 Kubernetes 用来存储所有对象元数据的 etcd 数据库中。当 Kubernetes 在 etcd 中存储密钥时,它会以 base64 编码格式存储它们。Base64 是一种以模糊方式编码数据的方式,但这并不是一种安全的加密方法。任何能够访问 base64 编码数据的人都可以轻松地解码它。AKS 在此基础上增加了一层安全性,通过在 Azure 平台内对所有数据进行静态加密。

Kubernetes 中的默认密钥实现允许你存储多种类型的密钥:

  • 不透明密钥:这些可以包含任何用户定义的任意密钥或数据。

  • 服务帐户令牌:这些由 Kubernetes pod 用于内置集群 RBAC。

  • Docker 配置密钥:这些用于存储 Docker 注册表凭据,以进行 Docker 命令行配置。

  • 基本认证密钥:这些用于存储以用户名和密码形式的认证信息。

  • SSH 认证秘密:用于存储 SSH 私钥。

  • TLS 证书:用于存储 TLS/SSL 证书。

  • 引导令牌秘密:用于存储在创建新集群或将新节点加入现有集群时使用的令牌。

作为 Kubernetes 的用户,你最常接触的通常是透明的秘密和 TLS 证书。你已经在 第六章,用 HTTPS 保护你的应用程序 中使用过 TLS 秘密。在本章中,你将重点关注不透明的秘密。

Kubernetes 提供了三种创建秘密的方法,具体如下:

  • 从文件创建秘密

  • 从 YAML 或 JSON 定义创建秘密

  • 从命令行创建秘密

使用上述任何方法,你都可以创建任何类型的秘密。

Kubernetes 提供了两种使用秘密的方法:

  • 将秘密作为环境变量使用

  • 在 pod 中将秘密挂载为文件

在接下来的章节中,你将通过这里提到的三种方式创建秘密,之后你将使用这里列出的两种方法来使用这些秘密。

在 Kubernetes 中创建秘密

在 Kubernetes 中,有三种不同的创建秘密的方法:通过文件、通过 YAML 或 JSON 定义,或直接通过命令行。让我们从通过文件创建秘密开始探索。

从文件创建秘密

在 Kubernetes 中创建秘密的第一种方法是通过文件创建。在这种方法中,文件的内容将成为秘密的值,而文件名将是每个值在秘密中的标识符。

假设你需要存储一个 URL 和一个用于访问 API 的安全令牌。要实现这一点,请按照以下步骤操作:

  1. 将 URL 存储在 secreturl.txt 中,如下所示:

    echo https://my-url-location.topsecret.com \
      > secreturl.txt
    
  2. 将令牌存储在另一个文件中,如下所示:

    echo 'superSecretToken' > secrettoken.txt
    
  3. 让 Kubernetes 从文件中创建秘密,如下所示:

    kubectl create secret generic myapi-url-token \
      --from-file=./secreturl.txt --from-file=./secrettoken.txt
    

    请注意,你正在 Kubernetes 中创建一个单一的秘密对象,引用了两个文本文件。在此命令中,你正在通过使用 generic 关键字创建一个不透明的秘密。

    命令应返回类似 图 10.1 的输出:

    使用 secreturl.txt 文件内容创建秘密

    图 10.1:创建一个不透明的秘密

  4. 你可以使用 get 命令检查秘密是否已经以与其他 Kubernetes 资源相同的方式创建:

    kubectl get secrets
    

    该命令将返回类似 图 10.2 的输出:

    使用 kubectl get secrets 命令验证创建的秘密

    图 10.2:已创建的秘密列表

    在这里,你将看到你刚刚创建的秘密,以及 default 命名空间中存在的其他秘密。这个秘密是 Opaque 类型,这意味着从 Kubernetes 的角度来看,内容的架构是未知的。它是一个任意的键值对,没有任何约束,与例如 SSH 认证或 TLS 秘密不同,后者有一个架构,并且会被验证是否包含所需的详细信息。

  5. 要了解有关机密的更多信息,你还可以运行 describe 命令:

    kubectl describe secrets myapi-url-token
    

    你将获得类似 图 10.3 的输出:

    使用 describe 命令获取机密的详细描述

    图 10.3:已创建机密的描述

    如你所见,前面的命令都没有显示实际的机密值。

  6. 要查看机密的值,你可以运行以下命令:

    kubectl get -o yaml secrets/myapi-url-token
    

    你将获得类似 图 10.4 的输出:

    使用 kubectl get secret 的 -o yaml 选项获取机密的编码值

    图 10.4:使用 kubectl get secret 的 -o yaml 选项获取机密的编码值

    数据以键值对的形式存储,文件名作为键,文件的 base64 编码内容作为值。

  7. 上述值是 base64 编码的。Base64 编码并不安全。它模糊了机密信息,使其不容易被操作员读取,但任何恶意行为者都可以轻松解码 base64 编码的机密。要获取实际的值,你可以运行以下命令:

    echo 'c3VwZXJTZWNyZXRUb2tlbgo=' | base64 -d
    echo 'aHR0cHM6Ly9teS1zZWNyZXQtdXJsLWxvY2F0aW9uLnRvcHNlY3JldC5jb20K'| base64 -d
    

    你将获得最初创建的机密的值:

使用 base 64 -d 命令解码 base64 编码的秘密

图 10.5:Base64 编码的机密可以轻松解码

这表明机密在默认的 Kubernetes 秘密存储中并没有被安全加密。

在本节中,你能够创建一个包含示例 URL 和安全令牌的机密,文件作为源。你还能够通过解码 base64 编码的机密来获取实际的机密值。

让我们继续探索创建 Kubernetes 秘密的第二种方法,即从 YAML 定义中创建秘密。

使用 YAML 文件手动创建机密

在上一节中,你是通过文本文件创建了机密。在本节中,你将通过以下步骤使用 YAML 文件创建相同的机密:

  1. 首先,你需要将机密编码为 base64,如下所示:

    echo 'superSecretToken' | base64
    

    你将获得以下值:

    c3VwZXJTZWNyZXRUb2tlbgo=
    

    你可能会注意到,这是在上一节获取机密的 yaml 定义时出现的相同值。

  2. 类似地,对于 url 值,你可以获得 base64 编码的值,如下所示:

    echo 'https://my-secret-url-location.topsecret.com' | base64
    

    这将给你 base64 编码的 URL:

    aHR0cHM6Ly9teS1zZWNyZXQtdXJsLWxvY2F0aW9uLnRvcHNlY3JldC5jb20K
    
  3. 现在你可以手动创建机密定义,然后保存文件。此文件已经在代码包中作为 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=
    

    让我们查看这个文件:

    • Opaque 机密,意味着从 Kubernetes 的角度来看,值是无约束的键值对。

    • 第 7-8 行:这些是机密的 base64 编码值。

    你可能会注意到,这个 YAML 文件与上一节中你得到的返回结果非常相似。这是因为你用来创建 Kubernetes 中机密的对象在 Kubernetes API 中存储时包含了更多的元数据。

  4. 现在,你可以像使用任何其他 Kubernetes 资源一样,通过使用 create 命令来创建机密:

    kubectl create -f myfirstsecret.yaml
    

    这将返回一个类似于图 10.6的输出:

    从 YAML 文件创建 Secret

    图 10.6:通过 YAML 文件成功创建的 Secret

  5. 你可以通过以下命令验证 Secret 是否成功创建:

    kubectl get secrets
    

    这将显示一个类似于图 10.7的输出:

    使用 kubectl get secrets 命令验证 Secret 的创建

    图 10.7:已创建秘密的列表

  6. 你可以通过在之前描述的相同方式使用kubectl get -o yaml secrets myapiurltoken-yaml来仔细检查秘密是否相同。

这描述了在 Kubernetes 中创建秘密的第二种方式。在下一节中,你将学习通过 kubectl 中的字面量创建秘密的最终方法。

使用字面量在 kubectl 中创建通用秘密

创建秘密的第三种方法是使用literal方法,这意味着你在命令行上通过 kubectl 传递值。正如你在前面的例子中看到的,Kubernetes 中的单个秘密可以包含多个值。在创建使用literal方法的 Secret 的命令中,你使用语法--from-literal=<key>=<value>来标识秘密中的不同值:

  1. 要使用literal方法创建 Secret,请运行以下命令:

    kubectl create secret generic myapiurltoken-literal \
      --from-literal=token='superSecretToken' \
      --from-literal=url=https://my-secret-url-location.topsecret.com
    

    这将返回一个类似于图 10.8的输出:

    使用字面量值创建 Secret

    图 10.8:通过 kubectl 中的字面量值成功创建的 Secret

  2. 你可以通过运行以下命令来验证 Secret 是否已创建:

    kubectl get secrets
    

    这将给我们一个类似于图 10.9的输出:

使用 kubectl get secrets 命令验证 Secret 的创建

图 10.9:验证通过字面量方法创建的 Secret

因此,你已经通过字面量值创建了秘密,除了之前的两种方法。

在这一节中,你已经使用三种方法创建了 Kubernetes 的秘密。在下一节中,你将探索两种在 Pod 和应用中使用这些秘密的方法。

使用你的秘密

一旦创建了秘密,它们就需要与应用程序关联。这意味着 Kubernetes 需要以某种方式将秘密的值传递给正在运行的 Pods。Kubernetes 提供了两种将秘密链接到应用程序的方法:

  • 使用秘密作为环境变量

  • 将秘密挂载为文件

将秘密挂载为文件是应用中使用秘密的最佳方式。在本节中,我们将解释这两种方法,并且展示为什么第二种方法是最好的。首先,从作为环境变量访问秘密开始。

将秘密作为环境变量

你可以通过将秘密作为环境变量来使用它。然后可以在 Pod 定义中的containersenv部分引用这些秘密。你将使用之前创建的秘密,在 Pod 中学习如何在应用程序中使用它们:

  1. 您可以像在 pod-with-env-secrets.yaml 中提供的定义那样,配置带有环境变量机密的 pod:

    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
    

    让我们检查这个文件:

    • secreturl.txt 文件位于 myapi-url-token 秘密中。

    • secrettoken.txt 文件位于 myapi-url-token 秘密中。

    当 Kubernetes 在需要使用机密的节点上创建 pod 时,它会将机密存储在该主机的 tmpfs 中,这是一种临时文件系统,不会写入磁盘。当最后一个引用该机密的 pod 在该节点上停止运行时,机密会从该节点的 tmpfs 中删除。如果节点关闭或重启,tmpfs 将被清除。

  2. 现在让我们创建 pod 并查看是否可以访问这些机密:

    kubectl create -f pod-with-env-secrets.yaml
    
  3. 检查环境变量是否设置正确:

    kubectl exec -it secret-using-env -- sh
    echo $SECRET_URL
    echo $SECRET_TOKEN
    

    这应该显示类似于图 10.10的结果:

    在 pod 内访问机密

    图 10.10:您可以在 pod 内获取机密

  4. 您现在可以使用 exit 命令退出容器的 shell。

在这个示例中有几点需要注意。首先,请注意,当您访问环境变量时,您会返回机密的实际值,而不是 base64 编码的值。这是预期的行为,因为 base64 编码仅在 Kubernetes API 层应用,而不是在应用程序层。

需要注意的第二点是,您能够通过进入正在运行的容器并回显机密来访问该机密。为 Kubernetes 中的 pod 应用适当级别的 RBAC(角色基础访问控制)非常重要,以确保不是每个集群用户都能运行 exec 命令并打开 shell。

还要注意的是,无论是作为容器镜像的应用程序,还是 pod 定义中,都没有硬编码机密。机密是通过 Kubernetes 中的动态配置提供的。

最后需要注意的一点是,任何应用程序都可以通过引用适当的 env 变量来使用机密值。没有办法限制容器中哪些进程可以访问哪些环境变量。

关于作为环境变量使用的机密,有一点很重要,那就是当机密本身更新时,环境变量的值不会被更新。这可能会导致您遇到这样一种情况:在机密更新后创建的 pod 与更新前创建的 pod 在环境变量值上有所不同。

在本节中,您探讨了如何通过环境变量从正在运行的 pod 中访问机密。在下一节中,您将探讨如何通过文件来实现这一点。

机密作为文件

让我们看看如何将相同的机密作为文件而不是环境变量进行挂载:

  1. 您将使用以下 pod 定义来演示如何完成此操作。它位于pod-with-vol-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
    

    让我们更仔细地查看这个文件:

    • /etc/secrets 目录为只读。

    • env 定义,因为您不必为每个机密定义名称。然而,应用程序需要有特殊代码来读取文件的内容,以便正确加载它。

    • 让我们看看机密是否已经通过。使用以下命令创建 Pod:

      kubectl create -f pod-with-vol-secret.yaml
      
    • 输出挂载卷中文件的内容:

      kubectl exec -it secret-using-volume -- sh
      cd /etc/secrets/
      cat secreturl.txt
      cat secrettoken.txt
      

      图 10.11所示,机密出现在 Pod 中:

      输出挂载卷中包含机密的文件内容

      图 10.11:机密作为文件在我们的 Pod 中可用

    • 现在,您可以使用 exit 命令退出容器的 shell。

这里也有几个要注意的地方。首先,请注意机密再次以明文显示,而不是以 base64 编码的形式。

其次,由于机密作为文件挂载,因此文件系统权限适用于这些机密。这意味着您可以限制哪些进程可以访问这些文件的内容。

最后,作为文件挂载的机密将在机密更新时动态更新。

您现在已经学习了将机密传递给运行容器的两种方式。在下一部分中,将解释为什么将机密作为文件的方法是最佳实践。

为什么将机密作为文件是最好的方法

尽管将机密作为环境变量使用是常见做法,但将机密作为文件挂载更加安全。Kubernetes 将机密作为环境变量安全地处理,但容器运行时并不会安全地处理它们。为了验证这一点,您可以运行以下命令,在 Docker 运行时以明文查看机密:

  1. 首先,使用以下命令获取使用环境变量的 Pod 所在节点:

    kubectl describe pod secret-using-env | grep Node
    

    这应该显示实例 ID,如图 10.12所示:

    通过描述 Pod 获取节点的实例 ID

    图 10.12:获取实例 ID

  2. 接下来,获取正在运行的 Pod 的 Docker ID:

    kubectl describe pod secret-using-env | grep 'Container ID'
    

    这应该会给您容器 ID:

    获取正在运行的 Pod 的 Docker ID

    图 10.13:获取 Docker ID

  3. 最后,您将在运行容器的节点上执行一个命令,以显示作为环境变量传递的机密。首先,让我们设置几个您稍后会用到的变量:

    INSTANCE=<provide instance number>
    DOCKERID=<provide Docker ID>
    VMSS=$(az vmss list --query '[].name' -o tsv)
    RGNAME=$(az vmss list --query '[].resourceGroup' -o tsv)
    

    注意

    上一个命令假设您在订阅中有一个单节点池的 AKS 集群。如果不是这种情况,请将 VMSSRGNAME 的值更改为正在运行集群的规模集和资源组的名称。

  4. 根据您的节点版本,您将运行以下命令之一。对于运行 Kubernetes 版本 1.18.x 或更早版本的集群,运行以下命令:

    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
    

    这应该返回类似于图 10.14的输出:

在 Docker 运行时显示解码后的机密输出

图 10.14:机密在 Docker 运行时被解码

对于运行版本 1.19 或更高版本的集群,运行以下命令:

az vmss run-command invoke -g $RGNAME -n $VMSS --command-id \
RunShellScript --instance-id $INSTANCE --scripts \
"crictl inspect --output yaml $DOCKERID" \
-o yaml | grep SECRET

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

显示在 containerd 运行时解码的秘密的输出

图 10.15:在 containerd 运行时解码的秘密

这显示了容器运行时中的明文秘密,无论是 Docker(1.19 之前的 AKS 版本)还是 containerd(1.19 及以上的 AKS 版本)。

如你所见,秘密是在容器运行时命令中解码的。这意味着大多数日志系统将记录这些敏感的秘密。因此,建议将秘密作为文件使用,因为它们除了传递给 Pod 和应用程序外,不会以明文形式传递。

让我们确保清理在本示例中创建的资源:

kubectl delete pod --all
kubectl delete secret myapi-url-token \
myapiurltoken-literal myapiurltoken-yaml

现在,你已经使用 Kubernetes 的默认秘密机制探索了秘密,让我们继续使用更安全的选项,即 Azure Key Vault。

安装 Azure Key Vault 提供程序以使用 Secrets Store CSI 驱动程序

在上一节中,你探索了存储在 Kubernetes 中的本地秘密。这意味着它们在 Kubernetes API 服务器上是 base64 编码的。你在上一节中看到,base64 编码的秘密根本不安全。对于高度安全的环境,你将希望使用更好的秘密存储。

Azure 提供了一个符合行业标准的密钥和秘密存储解决方案,称为 Azure Key Vault。它是一个托管服务,可以轻松创建、存储和检索密钥和秘密,并提供对密钥和秘密访问的审计。

Kubernetes 社区维护一个名为 Kubernetes Secrets Store CSI 驱动程序的项目(github.com/kubernetes-sigs/secrets-store-csi-driver)。该项目允许你通过 CSI 驱动程序将外部机密存储与 Kubernetes 中的卷进行集成。容器存储接口(CSI)是 Kubernetes 与存储系统交互的标准化方式。Secret Store CSI 驱动程序有多个实现。目前的实现包括 Hashicorp Vault、Google Cloud Platform 和 Azure Key Vault。

微软维护了 Secret Store CSI 驱动程序的 Key Vault 实现,名为 Azure Key Vault 提供程序。这个实现允许用户从 Kubernetes 内部访问 Key Vault 秘密。它还与 Pod 身份集成,以限制对秘密的访问。可选地,此实现还可以将 Key Vault 秘密与 Kubernetes 秘密同步,以便在需要时将其用作环境变量。

注意

为了简洁起见,我们将 Azure Key Vault 提供程序称为 Key Vault 的 CSI 驱动程序。

在写作时,Key Vault 的 CSI 驱动程序仅作为开源项目提供,你可以将其安装在集群中。值得注意的是,未来这个解决方案可能会作为 AKS 的托管附加组件引入。如需更详细的最新信息,请参阅 GitHub 上的此问题:github.com/Azure/AKS/issues/1876

要使用 Key Vault 的 CSI 驱动程序,您需要做两件事。首先,您需要在集群上设置驱动程序。这是本节的目标。其次,您需要为每个需要访问的 Key Vault 秘密,在 Kubernetes 中创建一个名为 SecretProviderClass 的对象。您将在下一节中了解更多内容。

在本节中,您将设置 Key Vault 的 CSI 驱动程序。首先,您将创建一个新的用户分配的托管身份。然后,您将创建一个新的密钥库,并授予该用户分配的托管身份对密钥库的访问权限。最后,您将为您的集群设置 Key Vault 的 CSI 驱动程序。

让我们从创建新的托管身份开始。

创建托管身份

Key Vault 的 CSI 驱动程序支持多种从 Key Vault 获取数据的方式。建议您使用托管身份将 Kubernetes 集群与 Key Vault 连接。为此,您可以使用上一章中设置的 AAD pod 托管身份附加组件。在本节中,您将创建一个新的托管身份,以便稍后与 Key Vault 一起使用:

  1. 让我们创建一个新的托管身份。您将使用 Azure 门户来完成这一步。首先,在 Azure 搜索框中查找 managed identity,如图 10.16所示:在 Azure 搜索框中查找托管身份

    图 10.16:在 Azure 搜索框中查找托管身份

  2. 在弹出的面板中,点击顶部的 + 新建按钮。为了将本章的资源组织在一起,建议创建一个新的资源组。在弹出的面板中,点击创建新建按钮来创建一个新的资源组。命名为 csi-key-vault,如图 10.17所示:为创建新的托管身份创建新的资源组

    图 10.17:创建新的资源组

  3. 现在,选择您创建集群的区域作为托管身份的区域,并为其命名,如果按照示例,可以命名为 csi-key-vault,如图 10.18所示。完成后,点击审核 + 创建按钮,在最后的窗口中点击创建按钮来创建您的托管身份:为托管身份提供实例详细信息

    图 10.18:提供实例详细信息

  4. 托管身份创建完成后,点击转到资源按钮进入该资源。在这里,您需要复制将在下一步骤中使用的资源 ID。您可以在托管身份的属性面板中找到该 ID,如图 10.19所示:从托管身份的属性面板中获取资源 ID

    图 10.19:获取托管身份的资源 ID

  5. 现在,您准备将托管身份链接到您的 AKS 集群。为此,您将像上一章一样,在云命令行中运行一个命令。之后,您将验证该身份是否在集群中可用。让我们从链接身份开始:

    az aks pod-identity add --resource-group rg-handsonaks \
      --cluster-name handsonaks --namespace default \
      --name csi-to-key-vault \
      --identity-resource-id <Managed identity resource ID>
    
  6. 您可以通过运行以下命令验证您的身份是否成功链接到您的集群:

    kubectl get azureidentity
    

    这应该会生成类似于 图 10.20 的输出:

验证您的身份是否成功链接到您的集群

图 10.20:验证集群中身份的可用性

在本节中,您创建了一个新的托管标识,并使用 AAD Pod 托管标识附加组件将其链接到了您的 Kubernetes 集群。在下一节中,您将创建一个密钥保管库,并授予您创建的新标识对密钥保管库中秘密的访问权限。最后,您将在 Key Vault 中创建一个秘密,稍后从您的集群中尝试访问它。

创建密钥保管库

在前一节中,您设置了用于 Key Vault 的 CSI 驱动器的托管标识。在本节中,您将创建将使用的密钥保管库:

  1. 要开始创建过程,请在 Azure 搜索栏中查找Key vaults从 Azure 门户导航到 Key Vault 服务

    图 10.21:通过 Azure 门户导航到密钥保管库

  2. 单击“+ 新建”按钮开始创建过程:创建新密钥保管库

    图 10.22:单击“添加”按钮开始创建密钥保管库

  3. 提供创建密钥保管库的详细信息。在之前步骤创建的资源组中创建密钥保管库。密钥保管库的名称必须全局唯一,因此考虑在名称中添加您的缩写。建议您将密钥保管库创建在与您的集群相同的区域:提供订阅和实例详细信息以创建密钥保管库

    图 10.23:提供创建密钥保管库的详细信息

  4. 提供密钥保管库详细信息后,单击“下一步:访问策略 >”按钮,为托管标识授予对秘密的访问权限。单击“+ 添加访问策略”为您的托管标识授予权限,如 图 10.24 所示:添加新的访问策略

    图 10.24:添加访问策略

    在结果面板中,选择“秘密管理模板”,单击“选择主体”下的“未选择”按钮,在结果面板中查找您之前创建的csi-to-key-vault。最后,单击屏幕底部的“选择”,然后单击“添加”,如 图 10.25 所示:

    将访问策略中的秘密管理模板分配给 csi-to-key-vault 托管标识

    图 10.25:将秘密管理模板分配给您的托管标识

  5. 一旦您为此托管标识提供了权限,请单击“审阅 + 创建”按钮审阅并创建您的密钥保管库。单击“创建”按钮完成创建过程。

  6. 创建你的密钥库需要几秒钟时间。一旦密钥库创建完成,点击“转到资源”按钮,进入“Secrets”部分,点击“生成/导入”按钮以创建一个新密钥,如图 10.26所示:在密钥库中创建新密钥

    图 10.26:创建新密钥

  7. 在密钥创建向导中,提供密钥的详细信息。为了使这个演示更易于跟随,使用名称 k8s-secret-demo。给密钥一个容易记住的值,比如 secret-coming-from-key-vault。点击屏幕底部的“创建”按钮以创建密钥:

配置密钥

图 10.27:为新密钥提供详细信息

现在你在 Key Vault 中有了一个密钥,你可以继续在集群中安装实际的 Key Vault CSI 驱动程序。

安装 Key Vault CSI 驱动程序

在本节中,你将在集群中设置 Key Vault CSI 驱动程序。这将允许你在下一节中从 Key Vault 中检索密钥。安装过程很简单,正如你将在这里看到的:

  1. 安装 Key Vault CSI 驱动程序最简单的方法是使用 Helm,正如你之前所做的那样。请注意,在本书发布后,此功能可能会作为附加组件提供。为此,添加 Key Vault CSI 驱动程序的仓库:

    helm repo add csi-secrets-store-provider-azure \
      https://raw.githubusercontent.com/Azure/secrets-store-csi-driver-provider-azure/master/charts
    
  2. 一旦添加了仓库,你可以使用以下命令安装实际的 Key Vault CSI 驱动程序:

    helm install csi-secrets \
      csi-secrets-store-provider-azure/csi-secrets-store-provider-azure
    
  3. 为了验证安装是否成功,你可以通过以下命令验证 SecretProviderClass CRD 是否已被添加到你的集群中:

    kubectl get crd
    

    这将显示包含 SecretProviderClass CRD 的输出,如图 10.28所示:

验证 SecretProviderClass CRD 已被添加到集群中

图 10.28:SecretProviderClass CRD 已被添加到集群中

这标志着 Key Vault CSI 驱动程序的安装完成。在本节中,你首先创建了一个托管身份,然后创建了一个包含密钥的密钥库,最后在集群中设置了 Key Vault 的 CSI 驱动程序。

现在你已经准备好使用 Key Vault 的 CSI 驱动程序,下一节中你将开始使用它。

使用 Azure Key Vault 提供程序的 Secrets Store CSI 驱动程序

现在 Key Vault 的 CSI 驱动程序已经在你的集群中设置完成,你可以开始使用它了。在本节中,你将通过两个示例来演示如何使用 Key Vault 的 CSI 驱动程序。首先,你将使用它将一个密钥作为文件挂载到 Kubernetes 中。接下来,你还将使用它将 Key Vault 中的密钥同步到 Kubernetes 秘密,并将它们用作环境变量。

让我们开始第一个示例,如何将 Key Vault 密钥挂载为文件。

将 Key Vault 密钥挂载为文件

在这个第一个示例中,你将在集群中创建一个新的 SecretProviderClass。这个对象将允许你将 Key Vault 中的密钥链接到 Kubernetes 中的 Pod。之后,你将创建一个使用该 SecretProviderClass 的 Pod,并将密钥挂载到该 Pod 中。让我们开始吧:

  1. SecretProviderClass 需要你知道 Azure Active Directory 租户 ID。要获取此信息,请运行以下命令:

    az account show --query tenantId
    

    这将显示一个类似于图 10.29的输出。复制并粘贴此值,并将其存储在以后可以引用的文件中:

    获取用于创建 SecretProviderClass 的租户 ID

    1   apiVersion: secrets-store.csi.x-k8s.io/v1alpha1
    2   kind: SecretProviderClass
    3   metadata:
    4     name: key-vault-secret-file
    5   spec:
    6     provider: azure
    7     parameters:
    8       usePodIdentity: "true"
    9       keyvaultName: "<key vault name>"
    10      objects:  |
    11        array:
    12          - |
    13            objectName: k8s-secret-demo
    14            objectType: secret        
    15      tenantId: "<your tenant ID>"
    

    让我们研究一下这个文件:

    • SecretProviderClass

    • SecretProviderClass

    • 第 15 行:你的 AAD 租户的 AAD 租户 ID。

    确保根据你的环境值进行编辑。

  2. 你可以使用以下命令创建此 SecretProviderClass

    kubectl create -f secretproviderclass-file.yaml
    
  3. 一旦创建了 SecretProviderClass,你就可以继续创建一个引用该 SecretProviderClass 的 Pod。示例已提供在 pod-keyvault-file.yaml 文件中:

    1   kind: Pod
    2   apiVersion: v1
    3   metadata:
    4     name: csi-demo-file
    5     labels:
    6       aadpodidbinding: "csi-to-key-vault"
    7   spec:
    8     containers:
    9       - name: nginx
    10        image: nginx
    11        volumeMounts:
    12        - name: keyvault
    13          mountPath: "/mnt/secrets-store"
    14          readOnly: true
    15    volumes:
    16      - name: keyvault
    17        csi:
    18          driver: secrets-store.csi.k8s.io
    19          readOnly: true
    20          volumeAttributes:
    21            secretProviderClass: "key-vault-secret-file"
    

    让我们来看看这个文件的关键部分:

    • 第 5-6 行:在这里,你将 Pod 与之前创建的托管身份关联。

    • 第 11-14 行:在这里,你定义了要挂载密钥的位置。

    • 第 15-21 行:在这里,你定义了实际的卷和与 Key Vault 的链接。在第 21 行,你引用了之前创建的 SecretProviderClass。

  4. 你可以使用以下命令创建这个 Pod:

    kubectl create -f pod-keyvault-file.yaml
    
  5. 使用以下命令监控 Pod 的创建:

    kubectl get pods -w
    

    这应该会返回一个类似于图 10.30的输出:

    验证 csi-demo-file Pod 的状态是否变更为 Running

    图 10.30:csi-demo-file Pod 的状态变更为 Running

  6. 一旦 Pod 创建并运行,你可以使用 kubectl exec 命令在 Pod 中打开一个 Shell,并验证密钥是否存在:

    kubectl exec -it csi-demo-file -- sh
    cd /mnt/secrets-store
    cat k8s-secret-demo
    

    这应该会输出你在 Key Vault 中创建的密钥,如图 10.31所示:

    验证密钥是否已挂载到 Pod 中

    图 10.31:你在 Key Vault 中配置的密钥作为文件挂载在 Pod 中

    正如预期的那样,你能够将你在 Key Vault 中配置的密钥显示在 Kubernetes 中。

  7. 现在,你可以使用 exit 命令退出容器的 Shell。

如你所见,你成功地使用了 CSI 驱动程序从 Key Vault 获取密钥,并将其作为文件显示在 Pod 中。

你也可以将 Key Vault 中的密钥同步到 Kubernetes 中的密钥,然后在运行中的 Pod 中将它们作为环境变量使用。接下来和本章的最后一部分将会探讨这一点。

使用 Key Vault 密钥作为环境变量

在前一部分中,你看到如何将 Key Vault 密钥作为文件在 Pod 中访问。如本章之前所学,建议你将 Kubernetes 密钥作为文件使用。

然而,有些情况下你不能修改应用程序以将秘密用作文件,而必须将其用作环境变量。你可以通过使用 Key Vault 的 CSI 驱动程序来完成这一点,在本节中你将配置该驱动程序。请注意,为了让 CSI 驱动程序将 Key Vault 中的秘密同步到 Kubernetes 中的 Secrets,你需要将秘密作为卷挂载到 Kubernetes;不能仅依赖于秘密的同步。

让我们配置所有这些内容:

  1. 首先,你将创建 SecretProviderClass。示例代码已在本章的代码文件中提供,位于 secretproviderclass-env.yaml 文件中:

    1   apiVersion: secrets-store.csi.x-k8s.io/v1alpha1
    2   kind: SecretProviderClass
    3   metadata:
    4     name: key-vault-secret-env
    5   spec:
    6     provider: azure
    7     parameters:
    8       usePodIdentity: "true"
    9       keyvaultName: "<key vault name>"
    10      objects:  |
    11        array:
    12          - |
    13            objectName: k8s-secret-demo
    14            objectType: secret
    15      tenantId: "<tenant ID>"
    16    secretObjects:
    17    - secretName: key-vault-secret
    18      type: Opaque
    19      data:
    20      - objectName: k8s-secret-demo
    21        key: secret-content 
    

    让我们查看此文件与之前创建的文件有什么不同:

    • secretName:这指的是将在 Kubernetes 中创建的秘密名称。

    • objectName:这指的是第 13 行的 objectName,即 Key Vault 中秘密的名称。

    • key:这是 Kubernetes 中秘密的键名称。如本章前面所解释的,Kubernetes 中的单个秘密可以包含多个键。

    文件的其余部分与之前你创建的 SecretProviderClass 类似。

  2. 你可以使用以下命令创建这个 SecretProviderClass:

    kubectl create -f secretproviderclass-env.yaml
    
  3. 一旦 SecretProviderClass 创建完成,你可以继续创建一个引用该 SecretProviderClass 的 pod。你不能仅依赖于秘密的同步,必须将 SecretProviderClass 挂载才能使 CSI 驱动程序同步秘密。在 pod-keyvault-env.yaml 文件中提供了一个示例:

    1   apiVersion: v1
    2   kind: Pod
    3   metadata:
    4     name: csi-demo-env
    5     labels:
    6       aadpodidbinding: "csi-to-key-vault"
    7   spec:
    8     containers:
    9     - name: nginx
    10      image: nginx
    11      env:
    12        - name: KEYVAULT_SECRET
    13          valueFrom:
    14            secretKeyRef:
    15              name: key-vault-secret
    16              key: secret-content
    17      volumeMounts:
    18        - name: keyvault
    19          mountPath: "/mnt/secrets-store"
    20          readOnly: true
    21    volumes:
    22      - name: keyvault
    23        csi:
    24          driver: secrets-store.csi.k8s.io
    25          readOnly: true
    26          volumeAttributes:
    27            secretProviderClass: "key-vault-secret-env" 
    

    这个 pod 和你之前创建的 pod 的区别在于第 11 到第 16 行。这应该很熟悉,因为这是使用秘密作为环境变量的典型方式。

  4. 你可以使用以下命令创建这个 pod:

    kubectl create -f pod-keyvault-env.yaml
    
  5. 使用以下命令监控 Pod 的创建:

    kubectl get pods -w
    

    这应该返回类似于图 10.32的输出:

    监控 csi-demo-env Pod 创建过程

    图 10.32:等待 csi-demo-env pod 启动

  6. 一旦 pod 创建并运行,你可以使用 kubectl exec 命令打开 pod 中的 shell,并验证秘密是否存在:

    kubectl exec -it csi-demo-env -- sh
    echo $KEYVAULT_SECRET
    

    这应该输出你在 Key Vault 中创建的秘密,如图 10.33所示:

    你在 Key Vault 中配置的秘密作为环境变量使用

    图 10.33:你在 Key Vault 中配置的秘密作为环境变量使用

  7. 现在你可以使用 exit 命令退出容器的 shell。

  8. 最后,你还可以通过运行以下命令验证秘密是否在 Kubernetes 中创建:

    kubectl get secret
    

    这应该显示类似于图 10.34的输出:

    同步的秘密可以通过 kubectl get secret 命令访问

    图 10.34:Kubernetes 中的 key-vault-secret 秘密与 Key Vault 中的秘密同步

  9. 一旦没有挂载该 Secret 的 Pod 存在时,这个 Secret 就会消失。您可以通过以下命令验证这一点:

    kubectl delete -f pod-keyvault-env.yaml
    kubectl get secret
    

    这应该会显示类似于图 10.35的输出:

删除 Pod 会自动删除 Secret

图 10.35:删除 Pod 会自动删除 Secret

这显示了尽管您有一个 SecretProviderClass,尝试将 Key Vault 密钥同步到 Kubernetes 密钥,但该同步仅在 Pod 引用该 SecretProviderClass 并挂载密钥时发生。

在本节中,您已经能够将 Key Vault 中的一个密钥同步到 Kubernetes 中的密钥。您能够通过环境变量在 Pod 中访问该密钥的值。

本章也就此结束,关于 Kubernetes 中的密钥。让我们确保清理所有我们创建的对象:

kubectl delete -f .
helm delete csi-secrets
az aks pod-identity delete --resource-group rg-handsonaks \
  --cluster-name handsonaks --namespace default \
  --name csi-to-key-vault 
az group delete -n csi-key-vault --yes

一旦资源被删除,您就可以继续下一章关于 AKS 中网络安全的内容。

摘要

在本章中,您了解了 Kubernetes 中的密钥。您既使用了 Kubernetes 中的默认密钥机制,也使用了 Azure Key Vault 提供的 Secrets Store CSI 驱动程序。

本章开始时介绍了 Kubernetes 中的不同密钥类型。接下来,您使用 Kubernetes 中的不同机制创建了密钥。然后,您使用了两种访问密钥的方法:通过文件或通过环境变量。

之后,您创建了一个托管身份和一个密钥库,以便实验 Key Vault 的 CSI 驱动程序。您将其安装在集群中,并使用两种机制访问 Key Vault 中的密钥:通过文件或通过环境变量。

在下一章,您将进一步了解 AKS 中的网络安全。

第十二章:11. AKS 中的网络安全

网络安全是保护应用程序的关键活动。安全网络的目标,一方面是允许用户连接到你的应用程序并使用你提供的所有功能;另一方面,你还需要保护你的网络免受攻击者的侵害。这意味着要确保攻击者无法访问你网络的关键部分,即使他们获得了访问权限,这种访问也应该受到限制。

在 AKS 中谈到网络安全时,网络的保护有两个不同的层次。第一个是控制平面。控制平面指的是托管 Kubernetes API 的托管 Kubernetes 主服务器。默认情况下,控制平面是暴露在互联网的。你可以通过限制哪些公共 IP 地址可以访问它(使用名为 授权 IP 范围 的功能)或通过部署私有集群来保护控制平面,这意味着只有连接到你虚拟网络的机器才能访问控制平面。

要保护的第二个网络层是运行在集群上的工作负载。保护工作负载有多种方法。第一种方法是使用 Azure 网络功能,例如 Azure 防火墙或 网络安全组NSGs)。第二种保护工作负载的方法是使用 Kubernetes 功能,称为网络策略。

在本章中,你将探索保护 AKS 集群网络的不同方式。具体来说,本章包含以下几个部分:

  • AKS 中的网络和网络安全

  • 控制平面网络安全

  • 工作负载网络安全

由于 AKS 集群的大多数网络配置只能在集群创建时进行配置,因此你将在本章中创建和销毁多个集群。

让我们从探讨 AKS 中的网络和网络安全概念开始本章内容。

AKS 中的网络和网络安全

本节作为 AKS 中网络和安全概念的介绍。你将首先覆盖控制平面,然后是工作负载网络,再到网络安全。

控制平面网络

Kubernetes 集群的控制平面是承载集群 Kubernetes API 服务器、管理调度器并存储集群状态的基础设施。当你与 Kubernetes 集群交互时,例如通过使用 kubectl,你实际上是在向 Kubernetes API 服务器发送命令。在 AKS 中,控制平面由微软管理,并作为一项服务提供给你。

默认情况下,控制平面通过互联网暴露,并且任何连接到互联网的人都可以访问它。这并不意味着控制平面不安全。即便攻击者能够访问你的控制平面,他们仍然需要集群凭证才能对控制平面执行命令。

然而,许多组织仍然希望限制对其 AKS 集群控制平面的网络访问。AKS 中有两种功能可以帮助你限制对集群控制平面的网络访问。

第一个功能叫做授权 IP 地址范围。通过在你的 AKS 上配置授权 IP 地址范围,你可以设置哪些 IP 地址允许访问你的 API 服务器。这意味着,不允许访问你的 API 服务器的 IP 地址将无法与 API 服务器交互。这一点在图 11.1中有所说明:

配置授权 IP 范围定义了哪些 IP 地址可以访问你的 API 服务器

图 11.1:授权 IP 范围解释

限制网络访问控制平面的另一种方法是使用称为私有集群的功能。通过配置私有集群,你不会为控制平面分配一个公开可访问的地址。集群只能通过私有网络进行访问。要连接到控制平面,你需要使用一台连接到Azure 虚拟网络VNet)的机器。这台机器将通过 Azure 的功能——Azure Private Link 与控制平面通信。

Private Link 是 Azure 的一项功能,允许你使用虚拟网络中的私有 IP 地址连接到托管服务。在使用 Private Link 时,会在你的虚拟网络中创建一个 Private Link 端点。要连接到这个 Private Link 端点,你必须从托管在同一虚拟网络中的虚拟机,或者在对等虚拟网络中,或者通过连接到该虚拟网络的 VPN 或 Azure ExpressRoute 进行连接。在图 11.2中,你可以看到一个示例,展示了如何使用托管在同一虚拟网络中的虚拟机实现此功能。你可以看到,承载工作负载的节点池(1)以及连接到同一虚拟网络的虚拟机(2)可以连接到控制平面,但通过互联网连接的用户(3)无法连接:

Private Link 允许你使用虚拟网络中的私有 IP 地址连接到托管服务

图 11.2:私有集群解释

需要理解的是,授权 IP 地址范围和私有集群仅为 Kubernetes 控制平面提供网络安全;它们不会影响工作负载网络。工作负载网络将在下一部分讨论。

工作负载网络

你的工作负载在 AKS 中部署在一个虚拟网络中的集群上。在虚拟网络中配置和保护网络安全有很多方法。在这一部分,我们将介绍一些用于保护部署在虚拟网络中工作负载的网络安全配置选项。但这仅仅是对这些概念的介绍。在部署生产集群之前,请参考 AKS 文档,详细了解不同的配置选项:docs.microsoft.com/azure/aks/concepts-network

你首先需要选择一个网络模型来部署你的集群。这个配置对安全性的影响有限,但从网络的角度理解是很重要的。有两种选择:

  • Kubenet 网络(默认):使用 kubenet 网络时,集群节点从 VNet 中的一个子网获取 IP 地址。运行在这些节点上的 Pod 从一个覆盖网络中获取 IP 地址,这个覆盖网络使用与节点不同的地址空间。Pod 到 Pod 之间的网络通过网络地址转换NAT)来实现。kubenet 的好处是只有节点会消耗集群子网中的一个 IP 地址。

  • Azure 容器网络接口(CNI)网络(高级):使用 Azure CNI 时,Pod 和节点都会从集群创建所在的子网中获取 IP 地址。这个模型的好处是,Pod 可以直接被集群外部的资源访问。缺点是,你需要进行谨慎的 IP 地址规划,因为每个 Pod 都需要在集群子网中占用一个 IP 地址。

在这两种网络模型中,你可以选择在现有的虚拟网络(VNet)中创建集群,或者让 AKS 代表你创建一个新的 VNet。

第二个需要考虑的网络安全配置是通过外部防火墙来路由进出流量。这可以是一个 Azure 防火墙,也可以是第三方的网络虚拟设备NVA)。通过将流量路由到外部防火墙,你可以应用集中式的安全规则、进行流量检查,并记录流量访问模式。要配置这一点,你需要在集群子网上配置一个用户定义路由UDR),将流量从集群路由到外部防火墙。如果你想进一步了解,可以参考文档:docs.microsoft.com/azure/aks/limit-egress-traffic

另一个网络安全选项是使用 Azure 中的 NSG 来限制进出流量。默认情况下,当你在 AKS 中创建一个 LoadBalancer 类型的服务时,AKS 还会配置一个 NSG,允许来自任何地方的流量访问该服务。你可以在 AKS 中调整该 NSG 的配置,以限制哪些 IP 可以访问这些服务。

最后,你可以通过使用 Kubernetes 的一种功能——网络策略,来限制集群中的流量。网络策略是一个 Kubernetes 对象,它允许你配置哪些流量可以在特定的 Pod 上进行。通过网络策略,你可以保护 Pod 到 Pod 之间的流量、外部到 Pod 的流量,以及 Pod 到外部的流量。建议你主要将网络策略用于 Pod 到 Pod 之间的流量(也叫做东西向流量),而将外部到 Pod 或 Pod 到外部的流量(也叫做南北向流量)交给外部防火墙或 NSG 来处理。

AKS 在集群的网络策略配置上支持两种选项。您可以使用 Azure 网络策略或 Calico 网络策略。Azure 网络策略由 Microsoft 开发、维护和支持,而 Calico 网络策略作为一个开源项目开发,并由一个名为 Tigera 的公司提供可选的商业支持 (tigera.io/)。

在工作负载网络安全部分,您将为集群配置网络安全组和网络策略。配置外部防火墙超出了本书的范围;请参考之前提到的文档,了解更多关于此配置的信息。

控制平面网络安全

在本节中,您将探索两种保护 AKS 集群控制平面的方法:授权 IP 范围和私有集群。您将首先通过更新现有集群来使用授权的 IP 范围。

使用授权 IP 范围保护控制平面

在 AKS 上配置授权的 IP 范围将限制哪些公共 IP 地址可以访问 AKS 集群的控制平面。在本节中,您将为现有集群配置授权的 IP 范围。您将限制流量到一个随机的公共 IP 地址,以验证流量阻止功能是否正常工作。然后,您将配置 Azure Cloud Shell 中的 IP 地址为授权 IP,看看它是如何允许流量通过的。

  1. 首先,浏览到 Azure 门户并打开您的 AKS 集群面板。在左侧导航中选择“网络”选项。然后,选择“设置授权 IP 范围”旁边的复选框,并在下方的框中输入 IP 地址 10.0.0.0,如 图 11.3 所示。您并不使用这个 IP;此配置仅用于验证如果您的 IP 地址没有被授权,您将无法连接到 Kubernetes 控制平面。最后,点击屏幕顶部的保存按钮。在 AKS 集群的网络面板中配置授权的 IP

    图 11.3:配置授权的 IP

  2. 现在,打开 Azure Cloud Shell。在 Cloud Shell 中,执行以下命令:

    Watch kubectl get nodes
    

    最初,这可能仍然会返回节点列表,如 图 11.4 所示。这是因为在 AKS 上配置授权的 IP 范围需要几分钟时间。

    watch kubectl get nodes 命令的输出,最初仍显示节点列表

    图 11.4:命令最初可能仍然会显示节点列表

    然而,几分钟后,这条命令的输出应该会返回错误,如 图 11.5 所示。这是预期的结果,因为您已将对控制平面的访问权限限制了。

    错误显示您无法通过未授权的 IP 连接到控制平面

    图 11.5:错误显示您无法再连接到控制平面

  3. 你可以通过按 Ctrl + C 停止 watch 命令。现在,你将获得当前 Cloud Shell 会话使用的 IP 地址,然后将其配置为授权 IP。要获取当前 Cloud Shell 会话使用的 IP 地址,你可以连接到 icanhazip.com,这是一个简单的网站,会返回你的公共 IP 地址。执行以下命令来实现:

    curl icanhazip.com
    

    这将返回类似于图 11.6的输出:

    获取 Cloud Shell 使用的 IP 地址

    图 11.6:获取 Cloud Shell 使用的 IP 地址

  4. 现在,你可以将此 IP 地址配置为 AKS 中的授权 IP 地址。你可以像在步骤 1中那样,在 AKS 面板的网络部分中完成此操作。具体操作见图 11.7。确保点击屏幕顶部的保存按钮。通过 AKS 面板的网络部分将 Cloud Shell 使用的 IP 地址配置为授权 IP

    图 11.7:将 Cloud Shell 的 IP 地址配置为 AKS 中的授权 IP

  5. 现在,执行与之前相同的命令,以获取你 AKS 集群中的节点列表。

    watch kubectl get nodes
    

    最初,这可能仍然会返回你之前看到的错误,如图 11.8所示。这是因为 AKS 配置授权 IP 范围需要几分钟时间。

最初获取节点列表时出现错误,因为授权 IP 正在配置中

图 11.8:命令最初仍然会返回错误

然而,过了几分钟后,这个命令的输出应该返回一个节点列表,如图 11.9所示。这表明你已经成功配置了授权的 IP 范围。

现在,你可以通过授权的 IP 连接到 API 服务器

图 11.9:通过配置授权的 IP,现在你可以连接到 API 服务器

通过配置授权的 IP 范围,你可以确认当 Cloud Shell 的 IP 地址未被允许访问 Kubernetes 控制平面时,连接会超时。通过将 Cloud Shell 的 IP 地址配置为授权 IP,你成功连接到了控制平面。

在典型的生产环境中,你不会将 Cloud Shell 的 IP 地址配置为 AKS 集群的授权 IP,而是会配置你的 Kubernetes 管理员、数据中心或你使用的工具的已知 IP 地址或范围。这里使用 Cloud Shell 仅作为一个示例,展示其功能。

还有第二种保护控制平面的方法,即通过部署私有集群。你将在接下来的章节中完成此操作。

使用私有集群保护控制平面

通过在 AKS 中配置授权的 IP 范围,你可以限制哪些公共 IP 地址可以访问你的集群。你还可以通过部署私有集群,完全限制任何公共流量访问你的集群。私有集群只能通过私有连接访问,该连接是通过 Azure Private Link 建立的。

让我们从配置私有集群并尝试访问它开始:

  1. 私有集群功能只能在集群创建时启用。这意味着你必须创建一个新的集群。若要在免费试用订阅中执行此操作,你需要删除现有的集群。你可以使用以下命令在 Cloud Shell 中完成:

    az aks delete -n handsonaks -g rg-handsonaks -y
    

    这个命令将花费几分钟来完成。请耐心等待,直到它成功删除你之前的集群。

  2. 你现在可以创建一个新的集群了。因为在后续步骤中,你还将创建一个新的虚拟机来访问集群(如 图 11.2 所示),你将创建一个新的 VNet,而不是让 AKS 为你创建 VNet。要创建 VNet,请使用以下命令:

    az network vnet create -o table \
        --resource-group rg-handsonaks \
        --name vnet-handsonaks \
        --address-prefixes 192.168.0.0/16 \
        --subnet-name akssubnet \
        --subnet-prefix 192.168.0.0/24
    
  3. 你需要获取在 VNet 中创建的子网的 ID。要获取该 ID,请使用以下命令:

    VNET_SUBNET_ID='az network vnet subnet show \
      --resource-group rg-handsonaks \
      --vnet-name vnet-handsonaks \
      --name akssubnet --query id -o tsv'
    
  4. 你还需要一个具有在刚创建的子网中创建资源权限的托管身份。要创建托管身份并授予其访问子网的权限,请使用以下命令:

    az identity create --name handsonaks-mi \
      --resource-group rg-handsonaks
    IDENTITY_CLIENTID='az identity show --name handsonaks-mi \
      --resource-group rg-handsonaks \
      --query clientId -o tsv'
    az role assignment create --assignee $IDENTITY_CLIENTID \
      --scope $VNET_SUBNET_ID --role Contributor
    IDENTITY_ID='az identity show --name handsonaks-mi \
      --resource-group rg-handsonaks \
      --query id -o tsv' 
    

    上述代码将首先创建托管身份。之后,它会获取托管身份的客户端 ID 并授予对该子网的访问权限。在最后的命令中,它获取托管身份的资源 ID。

  5. 最后,你可以使用以下命令创建私有 AKS 集群。如你所见,你正在创建一个只包含一个节点的小型集群。这是为了在免费试用订阅下节省核心配额:

    az aks create \
      --resource-group rg-handsonaks \
      --name handsonaks \
      --vnet-subnet-id $VNET_SUBNET_ID \
      --enable-managed-identity \
      --assign-identity $IDENTITY_ID \
      --enable-private-cluster \
      --node-count 1 \
      --node-vm-size Standard_DS2_v2 \
      --generate-ssh-keys
    

    该命令创建一个新的 AKS 集群,并包含一些此前未在书中介绍的特殊配置。第一个新配置是 --vnet-subnet-id。这允许你在现有 VNet 中的现有子网中创建 AKS 集群。--enable-managed-identity 参数使集群能够使用托管身份,--assign-identity 参数配置使用哪个托管身份。最后一个新配置选项是 --enable-private-cluster,它将创建一个带有私有端点的私有集群。

  6. 上述命令将花费几分钟来完成。完成后,你可以尝试通过 Azure Cloud Shell 访问你的集群。然而,这将失败,因为 Azure Cloud Shell 并未部署在你的 VNet 中。让我们进一步探索。首先,获取集群凭据:

    az aks get-credentials -n handsonaks -g rg-handsonaks
    

    这将询问你是否要覆盖现有的 kubeconfig 文件两次。按下 y 键确认,如 图 11.10 所示:

    通过 az aks get-credentials 命令获取集群凭据

    kubectl get nodes
    

    这将返回一个错误,如图 11.11所示。这个错误是预期中的,因为你的 Cloud Shell 无法通过私有连接访问 Private Link 端点。

    显示无法再从 Cloud Shell 访问控制平面的错误

    图 11.11:显示无法再从 Cloud Shell 访问控制平面的错误

    注意

    在之前的命令中,你注意到你的 Cloud Shell 无法访问 Kubernetes API 服务器。实际上,可以将 Azure Cloud Shell 连接到 Azure 中的 VNet,并通过该方式连接到 Kubernetes API 服务器。你在本示例的接下来的步骤中不会使用这种方法,但如果你对这种方法感兴趣,请参考文档:docs.microsoft.com/azure/cloud-shell/private-vnet

  7. 如前言所述,当你创建一个私有 AKS 集群时,AKS 会使用名为 Private Link 的服务将控制平面连接到你的 VNet。你实际上可以在 Azure 门户中看到这个端点。在门户中查看私有端点时,搜索 Azure 搜索栏中的 Private Link,如图 11.12所示:通过 Azure 门户导航到 Private Link 中心

    图 11.12:在 Azure 搜索栏中搜索 Private Link

    在结果窗格中,点击“私有端点”以查看当前的 Private Link 端点。你应该能在这里看到一个名为 kube-apiserver 的私有端点,如图 11.13所示。在这里,你会看到指向集群以及创建该私有端点的子网的链接。

    你订阅中的私有端点

    图 11.13:你订阅中的私有端点

    Private Link 利用 Azure DNS 私有区域将集群的 DNS 名称与私有端点的私有 IP 地址关联起来。要查看 Azure DNS 私有区域,请通过 Azure 搜索栏搜索“Private DNS 区域”,如图 11.14所示:

    通过 Azure 搜索栏导航到私有 DNS 区域

    图 11.14:通过 Azure 门户导航到私有 DNS 区域

    在结果窗格中,你应该能看到一个私有 DNS 区域。如果点击该区域,你将看到更多来自 DNS 区域的详细信息,如图 11.15所示。你会看到这里为你的集群 DNS 名称创建了一个 DNS 记录,该记录指向你 VNet 中的一个私有 IP 地址。

    显示 Azure 私有 DNS 区域中的 DNS 记录

    图 11.15:由 AKS 创建的 Azure DNS 私有区域中的 DNS 记录

  8. 为了建立与控制平面的私有连接,你将创建一个新的虚拟机,并使用它连接到控制平面。为了方便管理,你将在一个新的资源组中创建这个虚拟机,这样以后删除虚拟机会更容易。使用以下命令在 VNet 中创建一个新的子网,并在该子网中创建虚拟机:

    az network vnet subnet create \
      --resource-group rg-handsonaks \
      --vnet-name vnet-handsonaks \
      --name vmsubnet \
      --address-prefix 192.168.1.0/24
    VM_SUBNET_ID='az network vnet subnet show \
      --resource-group rg-handsonaks \
      --vnet-name vnet-handsonaks \
      --name vmsubnet --query id -o tsv'
    az group create -l <your Azure location> \
      --name rg-handsonaks-vm
    az vm create --name vm-handsonaks \
      --resource-group rg-handsonaks-vm \
      --image UbuntuLTS \
      --admin-username azureuser \
      --ssh-key-values ~/.ssh/id_rsa.pub \
      --subnet $VM_SUBNET_ID \
      --size Standard_D2_v2
    

    创建虚拟机大约需要一分钟。一旦创建完成,你应该会看到类似于图 11.16的输出。复制输出中的公共 IP 地址:

    创建新虚拟机并获取其公共 IP 地址

    图 11.16:创建新虚拟机并获取其公共 IP 地址

  9. 现在虚拟机已创建,你将把包含集群凭据的 Kubernetes 配置文件移动到该虚拟机。这避免了你必须在目标机器上安装 Azure CLI 来获取 Kubernetes 凭据。确保将<public IP>替换为上一步的结果。

    scp ~/.kube/config azureuser@<public IP>:~
    

    系统将提示你是否信任此主机。通过输入yes确认。这将生成类似于图 11.17的输出:

    将 Kubernetes 配置文件复制到虚拟机

    图 11.17:将 Kubernetes 凭据复制到目标机器

  10. 现在你可以使用以下命令访问远程机器:

    ssh azureuser@<public IP>
    
  11. 现在你已连接到远程机器,你需要使用kubectl。下载它,使其可执行,并使用以下命令将其移动到二进制文件夹中:

    curl -LO https://dl.k8s.io/release/v1.20.0/bin/linux/amd64/kubectl
    chmod +x kubectl 
    sudo mv ./kubectl /usr/local/bin/kubectl
    
  12. 为了让kubectl识别你上传的配置文件,你需要将其移动到kube目录中。你可以使用以下命令来完成:

    mkdir .kube
    mv config .kube/config
    
  13. 现在你已配置虚拟机以连接到集群,你可以通过执行以下命令来验证是否可以连接到集群:

    kubectl get nodes
    

    这应该会生成类似于图 11.18的输出:

    使用 kubectl get nodes 命令验证连接到集群

    图 11.18:从同一 VNet 中的虚拟机访问私有 AKS 集群

  14. 你还可以验证虚拟机用于连接集群的 DNS 记录。为此,首先使用nslookup命令获取 DNS 记录。你可以使用以下命令来执行此操作:

    kubectl cluster-info
    nslookup <cluster FQDN>
    

    这应该会生成类似于图 11.19的输出:

获取集群的 FQDN 并使用 nslookup 查找其 IP 地址

图 11.19:获取集群的 FQDN 并使用 nslookup 查找其 IP 地址

图 11.19所示,从nslookup命令返回的地址是一个私有 IP 地址。这意味着只有连接到该 VNet 的机器才能连接到 Kubernetes 控制平面。

现在,你已经成功创建了一个 AKS 私有集群,并验证了只有连接到 AKS VNet 的机器才能连接到控制平面。你无法从 Azure Cloud Shell 连接到控制平面,但可以从同一 VNet 中的虚拟机连接。由于你现在已经部署了一个私有集群,暂时不要删除正在使用的虚拟机。你将在下一个示例中使用它。在本章的最后一个示例中,你将删除此私有集群和虚拟机。

本节关于控制平面安全的内容到此结束。你已经了解了授权的 IP 范围和私有集群。下一节,你将进一步了解如何保护你的工作负载。

工作负载网络安全

你现在已经了解了如何保护 AKS 集群控制平面的网络安全。然而,这并没有影响到工作负载的网络安全。在本节中,你将探讨三种保护工作负载的方法。首先,你将使用 Azure 内部负载均衡器创建一个 Kubernetes 服务。接着,你将使用 NSG 来保护 Kubernetes 中服务的流量。最后,你将使用网络策略来保护 Pod 到 Pod 的流量。

使用内部负载均衡器保护工作负载网络

Kubernetes 有多种类型的服务,正如你在第三章在 AKS 上部署应用程序》中所学的那样。你之前多次使用过服务类型为负载均衡器,以便让 AKS 创建一个 Azure 负载均衡器。这些负载均衡器一直是公共负载均衡器。你还可以将 AKS 配置为创建一个内部负载均衡器。这在你创建的服务仅需要从 VNet 或与该 VNet 连接的网络内访问时非常有用。

在本节中,你将创建这样的一个服务:

  1. 如果你不再连接到之前示例中创建的虚拟机,请重新连接。你可以使用以下命令获取虚拟机的公共 IP 地址:

    az vm show -n vm-handsonaks \
      -g rg-handsonaks-vm -d --query publicIps
    

    你可以使用以下命令连接到虚拟机:

    ssh azureuser@<public IP>
    
  2. 一旦连接到此虚拟机,你需要获取与本书相关联的 git 仓库。你可以使用以下命令来获取:

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

    一旦仓库被克隆,使用以下命令进入本章的示例:

    cd Hands-On-Kubernetes-on-Azure-Third-Edition/Chapter11
    
  3. 本节中的示例应用程序将使用你在本书前半部分已经使用过的 guestbook 应用程序。然而,你之前使用的单一 YAML 文件已被拆分为两个文件:guestbook-without-service.yamlfront-end-service-internal.yaml。这样做的目的是为了让你更方便地查看与服务相关的配置。

    front-end-service-internal.yaml 文件包含使用 Azure 内部负载均衡器创建 Kubernetes 服务的配置。以下代码是该示例的一部分:

    1   apiVersion: v1
    2   kind: Service
    3   metadata:
    4     name: frontend
    5     annotations:
    6       service.beta.kubernetes.io/azure-load-balancer-internal: "true"
    7     labels:
    8       app: guestbook
    9       tier: frontend
    10  spec:
    11    type: LoadBalancer
    12    ports:
    13    - port: 80
    14    selector:
    15      app: guestbook
    16      tier: frontend
    

    你在 YAML 代码中使用注解来指示 AKS 创建一个 Azure 内部负载均衡器。你可以看到在前面的代码示例中的第 5-6 行,你将 service.beta.kubernetes.io/azure-load-balancer-internal 注解设置为 true

    你可以通过执行以下命令来创建 guestbook 应用程序和使用内部负载均衡器的服务:

    kubectl create -f guestbook-without-service.yaml
    kubectl create -f front-end-service-internal.yaml
    

    你可以然后通过以下命令获取服务,并等待它获得外部 IP:

    kubectl get service -w
    

    这将返回类似于图 11.20的输出:

    获取前端服务的外部 IP 地址,前端服务是一个私有 IP

    图 11.20:获取服务的外部 IP

  4. 如您所见,服务具有作为外部 IP 的私有 IP。您只能从集群部署到的 VNet 或与该 VNet 连接的其他网络访问此 IP。

    注意

    curl <external IP>
    

    这将返回与图 11.21类似的结果:

    使用 curl 命令访问前端服务

    图 11.21:通过内部负载均衡器访问服务

  5. AKS 创建了一个内部负载均衡器来公开此服务。您还可以在 Azure 门户中看到这个负载均衡器。要查看此内部负载均衡器,请首先在 Azure 搜索栏中搜索负载均衡器,如图 11.22所示:在 Azure 搜索栏中搜索负载均衡器

    图 11.22:通过 Azure 门户导航到负载均衡器

  6. 在结果窗格中,您应该看到两个负载均衡器,如图 11.23所示:显示两个负载均衡器的输出,一个称为 kubernetes,另一个称为 Kubernetes-internal

    图 11.23:默认目录中负载均衡器的列表

  7. 单击 kubernetes-internal 负载均衡器。这将带您进入类似图 11.24的窗格:kubernetes-internal 负载均衡器的概述。私有 IP 地址与之前用于连接前端服务的地址相同

    图 11.24:内部负载均衡器的详细信息

    在这里,您可以看到此内部负载均衡器的详细信息。正如您在屏幕截图中看到的亮点所示,与kubectl命令的输出相同的 IP 配置在负载均衡器上。

  8. 这结束了使用内部负载均衡器的示例。您现在可以通过应用以下命令删除服务使用内部负载均衡器:

    kubectl delete -f front-end-service-internal.yaml
    kubectl delete -f guestbook-without-service.yaml
    

    这将删除 guestbook 应用程序和服务。删除服务时,将删除 Kubernetes 中的服务以及 Azure 中的内部负载均衡器。这是因为一旦集群中不再需要内部负载均衡器的服务,AKS 将删除该内部负载均衡器。

在本节中,您使用内部负载均衡器部署了一个 Kubernetes 服务。这使您能够创建不向互联网公开的服务。然而,有时您需要将服务暴露给互联网,但需要确保只有信任的方可以连接。在下一节中,您将学习如何在 AKS 中创建使用网络安全组限制入站流量的服务。

使用网络安全组保护工作负载网络

到目前为止,你已经在 Kubernetes 中暴露了多个服务。你使用 Kubernetes 中的服务对象以及 Ingress 来暴露它们。然而,除了在上一节通过部署内部负载均衡器来限制访问之外,你从未限制过应用程序的访问。这意味着该应用程序始终可以公开访问。在接下来的示例中,你将创建一个具有公共 IP 的 Kubernetes 集群服务,但你将通过 AKS 配置的 NSG 限制对其的访问。

  1. 在本节的示例应用中,你将再次使用留言簿应用。与上一节相同,前端服务的配置已移到一个单独的文件中。对于这个示例,你将从使用front-end-service.yaml文件创建服务开始,稍后会使用front-end-service-secured.yaml文件更新它。

    首先,通过应用以下命令部署应用,保持原样,不做任何 NSG 配置:

    kubectl apply -f guestbook-without-service.yaml
    kubectl apply -f front-end-service.yaml
    

    然后,使用以下命令获取前端服务的 IP 地址:

    kubectl get service -w
    

    这将生成类似于图 11.25的输出。一旦获取到公共 IP,你可以通过按 Ctrl + C 来退出命令:

    获取前端服务的 IP 地址

    图 11.25:获取前端服务的 IP 地址

    现在,你可以通过浏览器或通过虚拟机本身连接到此服务。如果你通过浏览器连接,你应该期望看到类似于图 11.26的输出:

    通过外部 IP 访问留言簿应用

    图 11.26:通过网页浏览器访问留言簿应用

  2. 你也可以通过命令行连接到这个应用。为此,请使用以下命令:

    curl <public IP>
    

    这应该返回一个类似于图 11.27的输出:

    通过命令行连接到留言簿应用

    图 11.27:通过命令行连接到留言簿应用

  3. 现在,让我们通过只允许你的浏览器连接到应用来配置前端服务的额外安全性。为此,你将需要你当前使用的公共 IP 地址。如果你不知道它,你可以访问www.whatismyip.com/来获取你的 IP 地址,如图 11.28所示:使用 https://www.whatismyip.com/ 获取你的 IP 地址

    1   apiVersion: v1
    2   kind: Service
    3   metadata:
    4     name: frontend
    5     labels:
    6       app: guestbook
    7       tier: frontend
    8   spec:
    9     type: LoadBalancer
    10    ports:
    11    - port: 80
    12    selector:
    13      app: guestbook
    14      tier: frontend
    15    loadBalancerSourceRanges:
    16    - <your public IP address>
    

    这个文件与之前在本书中创建的服务非常相似。然而,在第 15 行和第 16 行中,你现在看到 loadBalancerSourceRanges 和添加你自己的公共 IP 地址的选项。你可以在这里提供多个公共 IP 地址或范围;每个地址或范围前面会加上一个破折号。如果你希望输入单个 IP 地址而不是范围,则在该 IP 地址后附加 /32。你需要这样做,因为 Kubernetes 期望的是 IP 范围,而 /32 的范围表示单个 IP 地址。

    要在此文件中编辑或添加你自己的 IP 地址,请使用以下命令:

    vi front-end-service-secured.yaml
    

    在生成的应用程序中,使用箭头键导航到底部,按下 i 键进入 insert 模式,删除占位符,添加你的 IP 地址,然后在后面加上 /32。要关闭并保存文件,按 Esc 键,输入 :wq! 写入并关闭文件,最后按 Enter 键。示例如 图 11.29 所示:

    通过添加你自己的 IP 编辑 front-end-service-secured.yaml 文件

    图 11.29:带有 IP 地址的 front-end-service-secured.yaml 文件示例

  4. 你可以使用以下命令更新之前部署的现有服务:

    kubectl apply -f front-end-service-secured.yaml
    

    这将导致 AKS 更新与此集群关联的 NSG,只允许来自你的公共 IP 地址的流量。你可以通过再次浏览服务的 IP 地址来确认这一点,你应该能够看到 guestbook 应用程序。然而,如果你从虚拟机中重试之前的命令,你会看到连接最终超时:

    curl <public IP>
    

    这将在 2 分钟后超时,输出类似于 图 11.30 所示:

    虚拟机内的连接超时

    图 11.30:虚拟机内的连接超时

  5. 你也可以在 Azure 中验证 NSG 配置。要验证这一点,通过 Azure 搜索栏查找网络安全组,如 图 11.31 所示:在 Azure 搜索栏中搜索网络安全组

    图 11.31:通过 Azure 门户导航到网络安全组

  6. 在生成的面板中,你应该能看到两个 NSG。选择名称以 aks-agentpool 开头的一个,如 图 11.32 所示:从 NSG 页面选择所需的 NSG

    图 11.32:选择 aks-agentpool NSG

  7. 在该 NSG 的详细视图中,你应该能看到一个规则,允许来自你的 IP 地址到服务公共 IP 地址的流量,如 图 11.33 所示:NSG 包含一个规则,只允许来自先前定义的公共 IP 的流量

    图 11.33:NSG 包含一个规则,只允许来自先前定义的公共 IP 的流量

    注意,这个规则是由 AKS 创建并管理的;你不需要自己创建这个规则。

  8. 这里,我们已经完成了这个示例。现在清理部署、虚拟机(VM)和私有集群。在虚拟机内,使用以下命令删除应用程序:

    kubectl delete -f guestbook-without-service.yaml
    kubectl delete -f front-end-service-secured.yaml
    

    然后,使用 exit 命令退出虚拟机。这将把你带回到 Cloud Shell。现在,你可以删除私有集群和用于连接的虚拟机:

    az group delete -n rg-handsonaks-vm -y
    az aks delete -g rg-handsonaks -n handsonaks -y
    

通过向 Kubernetes 服务添加额外配置,你成功地限制了能够连接到你的服务的人。你能够确认,只有允许连接到该服务的公共 IP 地址才能连接,而来自其他 IP 地址的连接则会超时。

这是一个保护所谓的北南流量的示例,意味着来自外部到你集群的流量。你还可以为东西流量提供额外的保护,即集群内部的流量。为此,你将使用 Kubernetes 中的网络策略功能。你将在下一节中完成这项操作。

使用网络策略保护工作负载网络

在前一节中,你让 Kubernetes 在 Azure 中配置了一个 NSG 以保护北南流量。这是限制公共服务流量的好做法。在大多数情况下,你还需要保护东西流量,也就是在你的 Pod 之间的流量。这样,你可以确保如果潜在的攻击者获取了你应用的一部分访问权限,他们就无法轻易连接到应用的其他部分或其他应用。这也被称为防止横向移动。

为了保护 Pod 之间的流量,Kubernetes 提供了一种称为网络策略的功能。网络策略可以用来保护外部流量到你的 Pod、从你的 Pod 到外部的流量以及 Pod 之间的流量。由于你已经学习了如何保护外部流量到你的 Pod,本节将介绍如何使用网络策略来保护 Pod 到 Pod 之间的流量。

在 AKS 中,网络策略是在集群创建时需要配置的(截至目前是这种情况)。如果你的集群启用了网络策略,你可以在集群中创建新的网络策略对象。当没有网络策略选择特定 Pod 时,所有进入和离开该 Pod 的流量都将被允许。当你将网络策略应用于 Pod 时,根据配置,所有进入和/或离开该 Pod 的流量将被阻止,除非该网络策略允许该流量。

让我们试试看:

  1. 首先创建一个启用了网络策略的新集群。在这个示例中,你将创建一个启用了 Azure 网络策略的集群。你可以使用以下命令创建此新集群:

    az aks create \
      --resource-group rg-handsonaks \
      --name handsonaks \
      --enable-managed-identity \
      --node-count 2 \
      --node-vm-size Standard_DS2_v2 \
      --generate-ssh-keys \
      --network-plugin azure \
      --network-policy azure
    
  2. 创建集群后,请确保刷新凭据以获取对集群的访问权限。你可以使用以下命令来完成此操作:

    az aks get-credentials -g rg-handsonaks -n handsonaks
    

    这将提示你覆盖现有凭据。你可以通过在两个提示中输入 y 来确认,如图 11.34所示:

    通过覆盖凭据来刷新凭据,以便获取对集群的访问权限

    图 11.34:获取新集群的凭据

  3. 在这个示例中,你将测试运行 nginx 的 Pod 中两个 Web 服务器之间的连接。这些代码已提供在 web-server-a.yamlweb-server-b.yaml 文件中。以下是 web-server-a.yaml 的代码:

    1   apiVersion: v1
    2   kind: Pod
    3   metadata:
    4     name: web-server-a
    5     labels:
    6       app: web-server
    7       env: A
    8   spec:
    9     containers:
    10    - name: webserver
    11      image: nginx:1.19.6-alpine
    

    这是 web-server-b.yaml 的代码:

    1   apiVersion: v1
    2   kind: Pod
    3   metadata:
    4     name: web-server-b
    5     labels:
    6       app: web-server
    7       env: B
    8   spec:
    9     containers:
    10    - name: webserver
    11      image: nginx:1.19.6-alpine
    

    如你所见,每个 Pod 的代码中都有一个标签 app,web-server,以及另一个名为 env 的标签,值分别为每个服务器的标识(A 对应 web-server-a,B 对应 web-server-b)。稍后你将在本示例中使用这些标签,以选择性地允许这些 Pod 之间的流量。

    要创建这两个 Pod,使用以下命令:

    kubectl create -f web-server-a.yaml
    kubectl create -f web-server-b.yaml
    

    在继续之前,通过运行以下命令验证 Pod 是否正在运行:

    kubectl get pods -w
    

    这应该返回类似于 图 11.35 的输出:

    验证两个创建的 Pod 是否正在运行

    图 11.35:两个 Pod 都在运行

  4. 在本示例中,我们将使用 Pod 的 IP 地址来测试连接。使用以下命令获取 web-server-b 的 IP 地址:

    kubectl get pods -o wide
    

    这应该返回类似于 图 11.36 的输出,其中你会看到突出显示的 IP 地址:

    获取 web-server-b Pod 的 IP 地址以测试连接

    图 11.36:获取 web-server-b 的 IP 地址

  5. 现在,尝试从 web-server-a 连接到 web-server-b。你可以使用以下命令测试此连接:

    kubectl exec -it web-server-a -- \
      wget -qO- -T 2 http://<web-server-b IP>
    

    这应该返回类似于 图 11.37 的输出:

    验证 web-server-a 是否能连接到 web-server-b

    图 11.37:验证 web-server-a 是否能连接到 web-server-b

  6. 现在,让我们创建一个新的 NetworkPolicy 对象,限制所有流量进出带有标签 app 为 web-server 的 Pod。此策略已在 deny-all.yaml 文件中提供:

    1   kind: NetworkPolicy
    2   apiVersion: networking.k8s.io/v1
    3   metadata:
    4     name: deny-all
    5   spec:
    6     podSelector:
    7       matchLabels:
    8         app: web-server
    9     ingress: []
    10    egress: []
    

    让我们探索一下代码中包含的内容:

    • NetworkPolicy 对象。

    • app: web-server

    • 第 9-10 行:在这里,你定义了允许规则。正如你所看到的,你并没有定义任何允许规则,这意味着所有流量将被阻止。

    在本示例稍后,你将添加更多具体的入口和出口规则,以选择性地允许流量流动。

  7. 现在,让我们创建这个网络策略。你可以使用以下命令来创建:

    kubectl create -f deny-all.yaml
    

    这将返回类似于 图 11.38 的输出:

    创建网络策略

    图 11.38:创建网络策略

  8. 现在让我们再次测试 web-server-aweb-server-b 之间的连接。你可以使用以下命令进行测试。

    kubectl exec -it web-server-a -- \
      wget -qO- -T 2 http://<web-server-b IP>
    

    这应该返回类似于 图 11.39 的输出:

    流量不再在 web-server-a 和 web-server-b 之间流动

    图 11.39:流量不再在 web-server-a 和 web-server-b 之间流动

  9. 现在,你将创建另一个网络策略,选择性地允许从 web-server-aweb-server-b 的流量。此策略包含在 allow-a-to-b.yaml 文件中:

    1   kind: NetworkPolicy
    2   apiVersion: networking.k8s.io/v1
    3   metadata:
    4     name: allow-a-to-b
    5   spec:
    6     podSelector:
    7       matchLabels:
    8         app: web-server
    9     ingress:
    10    - from:
    11      - podSelector:
    12          matchLabels:
    13            env: A
    14    egress:
    15    - to:
    16      - podSelector:
    17          matchLabels:
    18            env: B
    

    让我们更深入地探索该文件与早期网络策略之间的差异:

    • env: A

    • env: B

    同时请注意,你正在以一个新名称创建这个网络策略。这意味着你将在集群中激活两个网络策略,选择带有标签app: web-server的 pod。deny-allallow-a-to-b两个网络策略都将在你的集群中存在,并且都适用于标签为app: web-server的 pod。网络策略本质上是累加的,这意味着如果其中任何一个策略允许流量,那么该流量就会被允许。

  10. 我们使用以下命令创建这个策略:

    kubectl create -f allow-a-to-b.yaml
    

    这将返回类似于图 11.40的输出:

    创建一个新的网络策略,允许来自 web-server-a 的流量到 web-server-b

    图 11.40:创建一个新的网络策略,允许来自 web-server-a 的流量到 web-server-b

  11. 让我们再次测试web-server-aweb-server-b之间的连接。你可以通过应用以下命令来进行测试:

    kubectl exec -it web-server-a -- \
      wget -qO- -T 2 http://<web-server-b IP>
    

    这应该返回类似于图 11.41的输出:

    流量再次在 web-server-a 和 web-server-b 之间流动

    图 11.41:流量再次被允许从 web-server-a 到 web-server-b

  12. 你现在已经允许了来自web-server-aweb-server-b的流量。然而,你并没有允许流量反向通过,这意味着从web-server-bweb-server-a的流量被阻止了。我们也来测试一下。要进行测试,使用以下命令获取web-server-a的 IP 地址:

    kubectl get pods -o wide
    

    这将返回类似于图 11.42的输出,其中web-server-a的 IP 地址已被高亮显示:

    获取 web-server-a 的 IP 地址

    kubectl exec -it web-server-b -- \
      wget -qO- -T 2 http://<web-server-a IP>
    

    这应该返回类似于图 11.43的输出:

    输出显示从 web-server-b 到 web-server-a 的流量未被允许

    图 11.43:从 web-server-b 到 web-server-a 的流量未被允许,符合预期

    正如你在图 11.43中看到的,从web-server-bweb-server-a的流量超时,显示该流量被阻止。

  13. 这结束了关于 Azure 网络策略的示例。在下一章中,你将再次创建一个新的集群,因此,作为本章的结尾,安全删除启用了网络策略的集群,使用以下命令:

    az aks delete -n handsonaks -g rg-handsonaks -y
    

现在你已经使用网络策略来保护 pod 之间的流量。你看到了默认策略如何拒绝所有流量,以及如何添加新策略来选择性地允许流量。你还看到,如果允许一个 pod 到另一个 pod 的流量,反向流量不会自动被允许。

小结

本章介绍了 AKS 中的多种网络安全选项。你探讨了如何保护控制平面和集群中的工作负载。

为了保护控制平面,你首先使用授权的 IP 范围验证只有允许的公共 IP 地址能够访问集群的控制平面。之后,你创建了一个新的私有集群,该集群只能通过私有连接访问。你使用 Azure Private Link 连接到这个私有集群。

之后,你还探讨了工作负载网络安全。最初,你部署了一个公共服务,该服务对所有用户开放。然后,你让 AKS 配置 Azure NSGs,仅允许特定的连接访问该服务。你验证了可以从你的机器连接到该服务,但无法从 Azure 中的虚拟机连接,符合预期。最后,你还在一个新集群中配置了 Kubernetes 网络策略。你利用这些策略来保护 Pod 之间的流量,成功地确保了集群中不同 Pod 之间的流量安全。

在下一章中,你将学习如何使用 AKS 通过 Azure Service Operator 创建 Azure 资源,例如 Azure MySQL 数据库。

第四部分:与 Azure 托管服务集成

到目前为止,你已经在Azure Kubernetes ServiceAKS)上运行了多个应用程序。这些应用程序总是自包含的,意味着整个应用程序可以在 AKS 上完整运行。将整个应用程序部署在 AKS 上有一些优点。你可以获得应用程序的可移植性,因为你可以将该应用程序轻松迁移到其他任何 Kubernetes 集群。此外,你对应用程序的端到端控制权也非常充分。

权力越大,责任也越大。将应用程序的部分功能卸载到 Azure 提供的某些 PaaS 服务中有其优势。例如,将数据库卸载到托管的 PaaS 服务后,你无需再关心数据库服务的更新,备份会自动执行,而且很多日志记录和监控都会开箱即用。

在接下来的章节中,你将学习更多关于多种高级集成以及它们带来的好处。在阅读完本节内容后,你应该能够安全地访问其他 Azure 服务,如 Azure SQL 数据库和 Azure Functions,并使用 GitHub Actions 执行持续集成与持续交付CI/CD)。

本节包含以下章节:

  • 第十二章将应用程序连接到 Azure 数据库

  • 第十三章Azure 安全中心与 Kubernetes 的集成

  • 第十四章无服务器函数

  • 第十五章AKS 的持续集成与持续部署

本节将从第十二章将应用程序连接到 Azure 数据库开始,你将在这一章中将应用程序连接到 Azure MySQL 数据库。

第十三章:12. 将应用程序连接到 Azure 数据库

在前几章中,你将应用程序的状态存储在集群中,无论是 Redis 集群还是 MariaDB。你可能记得,它们在高可用性方面都有一些问题。本章将带你通过连接到 Azure 托管 MySQL 数据库的过程。

我们将讨论使用托管数据库的好处,而不是在 Kubernetes 上运行StatefulSets。为了创建这个托管和管理的数据库,你将使用Azure 服务操作器ASO)。ASO 是一种通过 Kubernetes 集群内部创建 Azure 资源(如托管的 MySQL 数据库)的方法。在本章中,你将了解更多关于 ASO 项目的细节,并将在你的集群上设置和配置 ASO。

然后你将使用 ASO 在 Azure 中创建一个 MySQL 数据库。你将使用这个托管数据库作为 WordPress 应用的一部分。这将向你展示如何将应用程序连接到托管数据库。本章分为以下几个主题:

  • Azure 服务操作器

  • 在你的集群上安装 ASO

  • 使用 ASO 创建 MySQL 数据库

  • 使用 MySQL 数据库创建应用程序

我们从探索 ASO 开始。

Azure 服务操作器

在这一部分,你将了解更多关于 ASO 的内容。我们将从探索使用托管数据库与直接在 Kubernetes 上运行 StatefulSets 的优缺点开始,然后进一步了解 ASO 的细节。

到目前为止,你所经历的所有示例都是自包含的;也就是说,所有内容都在 Kubernetes 集群内部运行。几乎所有的生产应用程序都有状态,这些状态通常存储在数据库中。尽管大部分云无关的架构有很大优势,但当涉及到管理像数据库这样的有状态工作负载时,这会带来巨大的劣势。

当你在 Kubernetes 集群上运行自己的数据库时,你需要处理可扩展性、安全性、高可用性、灾难恢复和备份等问题。云服务提供商提供的托管数据库服务可以减轻你或你的团队执行这些任务的负担。例如,Azure Database for MySQL 提供企业级的安全性和合规性,内建高可用性和自动备份,且服务能够在几秒钟内进行扩展。最后,你还可以选择将灾难恢复配置为次要区域。

从 Azure 获取一个生产级别的数据库比在 Kubernetes 上设置并管理自己的数据库要简单得多。在接下来的部分中,你将探索如何使用 Kubernetes 来在 Azure 上创建这些数据库的方法。

什么是 ASO?

与如今的大多数应用程序一样,许多繁重的工作已经由开源社区(包括微软员工)为我们完成。微软意识到许多用户希望从 Kubernetes 中使用他们的托管服务,并且需要一种更简便的方式来使用 Kubernetes 部署中使用的方法。ASO 项目就是为了解决这个问题而创建的。

ASO 是一个于 2020 年启动的新项目,它继承了 Open Service Broker for Azure (OSBA) 项目。OSBA 是微软最初的实现,允许你在 Kubernetes 中创建 Azure 资源,但这个项目现在不再维护,已被弃用。ASO 实现了相同的功能,并且在积极维护和开发中。

ASO 包含两个部分:一组 CustomResourceDefinitions (CRDs) 和一个管理这些 CRDs 的控制器。CRDs 是 Kubernetes 的一组 API 扩展,允许你指定想要创建的 Azure 资源。资源组、虚拟机、MySQL 数据库等都有对应的 CRD。

ASO 中的大多数 API 仍处于 alpha 或 beta 阶段,意味着它们可能在未来发生变化。请参考文档 github.com/Azure/azure-service-operator 获取最新的资源定义,因为本章使用的定义可能已经发生变化。

控制器是一个运行在集群中的 pod,监视 Kubernetes API 中通过这些 CRDs 创建的任何对象。正是这个控制器与 Azure API 交互,并创建你使用 ASO 创建的资源。

ASO 依赖于另外两个你已经在本书中学到的项目,即 Azure Active Directory (Azure AD) pod 管理身份和 cert-manager。ASO 使用 Azure AD pod 管理身份将托管身份链接到 ASO pod。这也意味着该托管身份需要有权限来创建这些资源。ASO 使用 cert-manager 获取证书以供 ASO pod 使用。

默认情况下,ASO 会将连接字符串等秘密存储在 Kubernetes secrets 中。正如你在前几章中学到的,最好将秘密存储在 Key Vault 中,而不是 Kubernetes 中。ASO 也有将秘密存储在 Key Vault 中的选项,在设置过程中,你将配置 ASO 将秘密存储在 Key Vault 中。

从用户使用 ASO 的角度来看,图 12.1 描述了当你创建资源时发生的情况:

  1. 作为用户,你将一个 Azure 资源的 YAML 定义提交给 Kubernetes API。Azure 资源在 CRD 中定义。

  2. ASO pod 正在监视 Kubernetes API 中 Azure CRD 对象的变化。

  3. 当检测到变化时,ASO 将在 Azure 中创建资源。

  4. 如果在资源创建过程中生成了连接字符串,这个连接字符串将存储在 Kubernetes secret 中(默认情况下),或者存储在 Key Vault 中(如果已配置)。

使用 ASO 创建资源的高级过程图

图 12.1:使用 ASO 创建资源的高级过程图

在本节中,你已经了解了 ASO 项目的基本知识。在接下来的章节中,你将开始在集群上安装 ASO。

在集群上安装 ASO

要在集群上安装 ASO,你需要一个集群。在上一章结束时,你删除了集群,因此这里你需要创建一个新集群。之后,你需要创建一个托管身份和一个密钥库。设置 ASO 时,创建这两项资源是最佳实践,这就是本章将解释如何以这种方式设置 ASO 的原因。创建这些资源后,你需要确保 cert-manager 已在集群中设置。确认后,你可以使用 Helm chart 安装 ASO。

让我们从第一步开始,创建一个新的 AKS 集群。

创建一个新的 AKS 集群

由于你在上一章结束时删除了集群,接下来我们从创建一个新集群开始。你可以通过 Cloud Shell 执行所有这些步骤。让我们开始吧:

  1. 首先,你需要创建一个新集群。由于你将使用 pod 身份进行 ASO 授权,你还需要在新集群上启用 pod 身份附加组件。在写这篇文章时,pod 身份附加组件处于预览阶段。

    如果你还没有按照第九章Azure Active Directory 在 AKS 中的 pod 管理身份中的说明为你的订阅注册此预览,请使用以下命令进行注册:

    az feature register --name EnablePodIdentityPreview \
      --namespace Microsoft.ContainerService
    

    你还需要安装 Azure CLI 的预览扩展,可以使用以下命令进行安装:

    az extension add --name aks-preview
    

    你需要等到 pod 身份预览在你的订阅中注册完成。你可以使用以下命令来验证此状态:

    az feature show --name EnablePodIdentityPreview \
      --namespace Microsoft.ContainerService -o table
    

    等待状态显示为已注册,如图 12.2所示:

    显示注册状态的输出

    az provider register --namespace Microsoft.ContainerService
    
  2. 一旦你注册了预览提供程序,或者如果你已经在第九章Azure Active Directory 在 AKS 中的 pod 管理身份部分完成了此操作,你可以使用以下命令创建一个新集群:

    az aks create -g rg-handsonaks -n handsonaks \
      --enable-managed-identity --enable-pod-identity \
      --network-plugin azure --node-vm-size Standard_DS2_v2 \
      --node-count 2 --generate-ssh-keys 
    
  3. 一旦命令执行完成,使用以下命令获取凭证以访问你的集群:

    az aks get-credentials -g rg-handsonaks \
      -n handsonaks --overwrite-existing
    

现在你已经有了一个启用了 pod 身份的新 Kubernetes 集群。为了继续设置 ASO,让我们现在创建一个托管身份。

创建一个托管身份

在本节中,你将使用 Azure 门户创建一个托管身份。然后,你将授权 AKS 集群管理这个托管身份,并授予托管身份访问你的订阅,以便创建资源。让我们开始:

  1. 在 Azure 搜索栏中,查找托管身份,如图 12.3所示:在 Azure 搜索栏中搜索托管身份

    图 12.3:搜索托管身份

  2. 在结果页面中,点击+ 新建以创建新的托管身份,如图 12.4所示:点击“添加新建”按钮以创建新的托管身份

    图 12.4:创建新的托管身份

  3. 为了将本章的资源进行组织,创建一个名为 ASO 的新资源组,如图 12.5所示:创建新的资源组

    图 12.5:创建新的资源组

  4. 为托管身份提供位置和名称;如果你希望遵循此示例,请使用名称aso-mi,如图 12.6所示。确保选择与集群所在区域相同的区域:提供必要的详细信息以创建托管身份

    图 12.6:提供项目和实例详细信息以创建托管身份

  5. 点击屏幕底部的“审核 + 创建”并创建托管身份。

  6. 托管身份创建完成后,你需要捕获客户端 ID 和资源 ID 以备后用。复制并粘贴这些信息到可以稍后访问的位置。你可以在概览窗格中找到客户端 ID,如图 12.7所示:从托管身份窗格获取客户端 ID 详细信息

    图 12.7:从托管身份获取客户端 ID

    你可以在属性窗格中找到资源 ID,如图 12.8所示:

    从托管身份窗格获取资源 ID 详细信息

    图 12.8:获取托管身份的资源 ID

  7. 下一步是为托管身份赋予 AKS 集群权限。为此,点击左侧窗格中的“访问控制 (IAM)”,点击屏幕顶部的+ 添加按钮,点击下拉菜单中的“添加角色分配”,选择“托管身份操作员”角色,在“分配访问权限给”下拉菜单中选择“用户分配的托管身份”,然后选择 handsonaks-agentpool 身份并保存。此过程如图 12.9所示:为 AKS 集群赋予访问托管身份的权限

    图 12.9:为 AKS 赋予对托管身份的访问权限

  8. 现在,你将为托管身份赋予在你的订阅上创建资源的权限。为此,查找 Azure 搜索栏中的“订阅”,如图 12.10所示,然后选择你的订阅:在 Azure 搜索栏中查找订阅

    图 12.10:在 Azure 搜索栏中查找订阅

  9. 在订阅窗格中,点击“访问控制 (IAM)”,点击屏幕顶部的+ 添加按钮,点击“添加角色分配”,选择“贡献者”角色,在“分配访问权限给”下拉菜单中选择“用户分配的托管身份”,然后选择 aso-mi 身份并保存。此过程如图 12.11所示:

为 aso-mi 分配订阅级别的贡献者角色

图 12.11:为你的订阅赋予 aso-mi 权限

这完成了托管标识的设置。在下一节中,您将创建一个密钥保管库,并允许您刚刚创建的托管标识进行机密的创建和读取。

创建密钥保管库

在本节中,您将创建 ASO 将用于存储连接字符串和机密的密钥保管库。虽然在 ASO 设置过程中这是可选步骤,但我们推荐执行此操作。

  1. 首先,在 Azure 搜索栏中搜索“密钥保管库”,如图 12.12所示:在 Azure 搜索栏中搜索 Key Vault

    图 12.12:在 Azure 搜索栏中寻找密钥保管库

  2. 点击屏幕顶部的“+ 新建”按钮以创建新的密钥保管库。选择您之前创建的 ASO 资源组,并为您的密钥保管库命名。请注意,密钥保管库的名称必须唯一,如果不唯一,请考虑在名称中添加额外的字符。另外,确保在与 AKS 集群相同的区域创建密钥保管库。最终的配置如图 12.13所示:提供必要的详细信息以创建密钥保管库

    图 12.13:密钥保管库配置

  3. 现在选择“下一步:访问策略 >”以配置新的访问策略。在此处,您将为前面部分创建的 aso-mi 托管标识授予在此密钥保管库中进行机密管理的权限。为此,首先点击“+ 添加访问策略”按钮,如图 12.14所示:点击添加访问策略按钮

    图 12.14:点击“+ 添加访问策略”按钮

  4. 在弹出的窗口中,选择“机密管理”模板,然后点击“未选择”以选择您的托管标识。在接下来的弹窗中,找到 aso-mi 托管标识,选择它,然后点击“选择”,接着点击“添加”,如图 12.15所示:为托管标识添加机密管理权限

    图 12.15:为托管标识添加机密管理权限

  5. 这已配置了密钥保管库中的访问策略。现在点击“审核 + 创建”按钮,在最后的窗口中点击“创建”以创建密钥保管库。完成这一过程应该需要几分钟时间。

一旦您的密钥保管库部署完成,您就可以开始安装 ASO,下一节将详细说明如何操作。

在集群上设置 ASO

现在您已经具备了所需的托管标识和密钥保管库,您可以开始在集群上部署 ASO。您可以使用 Cloud Shell 执行所有这些步骤。让我们开始吧:

  1. 您在创建新的 AKS 集群一节中创建了一个新集群。您需要将之前创建的托管标识链接到该集群。ASO 组件将创建在它们自己的命名空间中,因此您还需要为此创建一个新的命名空间:

    kubectl create namespace azureoperator-system
    az aks pod-identity add --resource-group rg-handsonaks \
      --cluster-name handsonaks --namespace azureoperator-system \
      --name aso-identity-binding \
      --identity-resource-id <resource ID of managed identity>
    
  2. 现在你可以在集群上安装 cert-manager。你之前在第六章《用 HTTPS 保护你的应用》中已经安装过一次,但在该章结尾时要求你移除这个组件。你可以使用以下命令重新安装:

    kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml
    
  3. 使用以下命令跟踪 cert-manager 的部署状态:

    kubectl rollout status \
      -n cert-manager deploy cert-manager-webhook
    

    等待直到部署显示成功完成,如图 12.16所示:

    验证 cert-manager 部署状态的输出

    图 12.16:检查 cert-manager 的部署状态

  4. 一旦 cert-manager 完全部署完毕,你可以开始安装 ASO。首先使用以下命令添加 ASO 的 Helm 仓库:

    helm repo add azureserviceoperator \
    https://raw.githubusercontent.com/Azure/azure-service-operator/master/charts
    
  5. 接下来,你需要为 ASO 安装提供配置值。使用以下命令打开本章附带代码示例中的 values.yaml 文件:

    code values.yaml
    

    按照此处所示,填写该文件中的所有必需值:

    1   azureTenantID: "<tenant ID>"
    2   azureSubscriptionID: "<subscription ID>"
    3   azureOperatorKeyvault: "<key vault name>"
    4   azureClientID: "<client ID>"
    5   cloudEnvironment: AzurePublicCloud
    6   azureUseMI: true
    7   image:
    8     repository: mcr.microsoft.com/k8s/azureserviceoperator:0.1.16800
    9   installAadPodIdentity: true
    10  aad-pod-identity:
    11    azureIdentityBinding:
    12      name: aso-identity-binding
    13      selector: aso_manager_binding
    14    azureIdentity:
    15      enabled: True
    16      name: aso-identity
    17      type: 0
    18      resourceID: "<resource ID>"
    19      clientID: "<client ID>"
    

    如前面的代码示例所示,你需要提供租户 ID、订阅 ID、密钥库名称、托管身份的客户端 ID(两次)以及托管身份的资源 ID。你可以使用以下命令查找租户 ID 和订阅 ID:

    az account show
    

    这将返回类似图 12.17的输出,其中租户 ID 和订阅 ID 已经被突出显示:

    从输出中获取订阅 ID 和租户 ID

    图 12.17:获取订阅 ID 和租户 ID

  6. 一旦填入这些值,你可以使用以下命令安装 ASO:

    helm upgrade --install aso \
      azureserviceoperator/azure-service-operator \
      -n azureoperator-system --create-namespace \
      -f values.yaml
    
  7. 安装过程需要几分钟。等待直到以下命令返回成功的部署信息:

    kubectl rollout status deploy \
      -n azureoperator-system azureoperator-controller-manager
    

    输出应类似于图 12.18

    验证 ASO 部署状态的输出

    图 12.18:检查 ASO 部署状态

  8. 在写作时,azureoperator-controller-manager 部署的 aadpodidbinding 标签存在问题。不过,通过应用一个补丁可以修复此问题,给该部署应用一个新标签。这个补丁文件已经包含在本章的文件中,特别是在 patch.yaml 文件中:

    spec:
      template:
        metadata:
          labels:
            aadpodidbinding: aso-identity-binding
    

    如你所见,补丁本身会将一个新的标签应用到部署中的 pod。你可以使用以下命令应用这个补丁:

    kubectl patch deployment \
      azureoperator-controller-manager \
      -n azureoperator-system \
      --patch "$(cat patch.yaml)"
    

    这将确保你可以在下一部分中使用 ASO。

现在,ASO 已经在你的集群上部署完毕,你准备好开始使用 Kubernetes 和 ASO 部署 Azure 资源了。你将在下一节中进行操作。

使用 ASO 部署 Azure MySQL 数据库

在上一节中,你已经在 Kubernetes 集群上部署了 ASO。这意味着现在你可以使用 Kubernetes API 部署 Azure 资源。在本节中,你将使用 YAML 文件创建一个运行在 Azure MySQL 数据库服务上的 MySQL 数据库,并使用 kubectl 提交这些文件到 Kubernetes。让我们开始吧:

  1. 首先,你需要创建一个资源组。资源组定义的代码也可以在本章的代码示例中找到。创建此文件并将其保存为 rg.yaml

    apiVersion: azure.microsoft.com/v1alpha1
    kind: ResourceGroup
    metadata:
      name: aso-resources
    spec:
      location: <cluster location>
    

    如你所见,在资源的代码中,apiVersion 指向 azure.microsoft.com,而 kindResourceGroup。此外,你需要提供资源组的详细信息,包括其名称和位置。确保将 location 更改为你集群的位置。

    你可以使用以下命令创建该资源组:

    kubectl create -f rg.yaml
    

    若要监视资源组创建过程,可以使用以下命令:

    kubectl get resourcegroup -w
    

    这将返回一个类似于图 12.19的输出:

    确认成功创建新资源组的输出

    图 12.19:监视新资源组的创建

  2. 让我们还验证一下资源组是否已在 Azure 中创建。为此,请在 Azure 搜索栏中查找资源组名称(例如本示例中的 aso-resources),如图 12.20所示:在 Azure 搜索栏中搜索资源组

    图 12.20:在 Azure 门户中搜索资源组

    如你所见,资源组已在搜索结果中返回,表示资源组已成功创建。

  3. 现在你可以创建 MySQL 服务器了。你不会创建虚拟机来运行 MySQL,而是创建一个在 Azure 上管理的 MySQL 服务器。要创建此服务器,可以使用为你提供的 mysql-server.yaml 文件:

    1   apiVersion: azure.microsoft.com/v1alpha1
    2   kind: MySQLServer
    3   metadata:
    4     name: <mysql-server-name>
    5   spec:
    6     location: <cluster location>
    7     resourceGroup: aso-resources
    8     serverVersion: "8.0"
    9     sslEnforcement: Disabled
    10    createMode: Default
    11    sku:
    12      name: B_Gen5_1
    13      tier: Basic
    14      family: Gen5
    15      size: "5120"
    16      capacity: 1
    

    该文件包含 MySQL 服务器的特定配置。以下几个要点值得注意:

    • MySQLServer 实例。

    • location 为你集群的位置。

    • 本演示中已禁用 sslEnforcement。这样做是为了使演示更容易理解。如果你创建的是生产集群,强烈建议启用 sslEnforcement

    • 第 11-16 行:在这里你定义了 MySQL 服务器的大小。在这种情况下,你正在创建一个基本服务器,容量为 5 GB。如果你计划将其用于生产环境,可能需要更大的服务器。

    你可以使用以下命令创建 MySQL 服务器:

    kubectl create -f mysql-server.yaml
    

    这将需要几分钟时间才能完成。你可以使用以下命令跟踪进度:

    kubectl get mysqlserver -w 
    

    这将返回一个类似于图 12.21的输出:

    确认成功创建新 MySQL 服务器的输出

    图 12.21:监视 MySQL 服务器的创建

    如果在创建 MySQL 服务器时遇到错误,请参考 github.com/Azure/azure-service-operator/blob/master/docs/troubleshooting.md 上的 ASO 文档。

    一旦你收到服务器成功配置的消息,可以按 Ctrl + C 退出此命令。

  4. 在创建 MySQL 服务器之后,你可以创建 MySQL 数据库。MySQL 数据库的定义已在 mysql-database.yaml 文件中提供:

    1   apiVersion: azure.microsoft.com/v1alpha1
    2   kind: MySQLDatabase
    3   metadata:
    4     name: wordpress-db
    5   spec:
    6     resourceGroup: aso-resources
    7     server: <mysql-server-name>
    

    数据库的定义是提供一个名称并引用你之前创建的服务器。要创建数据库,你可以使用以下命令:

    kubectl create -f mysql-database.yaml
    

    这将需要几秒钟才能完成。你可以使用以下命令来跟踪进度:

    kubectl get mysqldatabase -w 
    

    这将返回类似于图 12.22的输出:

    确认 MySQL 数据库成功创建的输出

    图 12.22:监控 MySQL 数据库的创建

    一旦你收到数据库已成功配置的消息,你可以通过按Ctrl + C退出此命令。

  5. 你可以创建一个允许数据库流量的防火墙规则。在此示例中,你将创建一个允许来自所有来源的流量的规则。在生产环境中,不建议这样做。有关 Azure Database for MySQL 的推荐网络配置,请参阅文档:docs.microsoft.com/azure/mysql/flexible-server/concepts-networking。防火墙规则的配置已在 mysql-firewall.yaml 文件中提供:

    1   apiVersion: azure.microsoft.com/v1alpha1
    2   kind: MySQLFirewallRule
    3   metadata:
    4     name: allow-all-mysql
    5   spec:
    6     resourceGroup: aso-resources
    7     server: <mysql-server-name>
    8     startIpAddress: 0.0.0.0
    9     endIpAddress: 255.255.255.255
    

    如你所见,我们引用了先前创建的 MySQL 服务器,并允许来自所有 IP 地址的流量(即从 0.0.0.0255.255.255.255)。

    要创建防火墙规则,你可以使用以下命令:

    kubectl create -f mysql-firewall.yaml
    

    这将需要几秒钟才能完成。你可以使用以下命令来跟踪进度:

    kubectl get mysqlfirewallrule -w 
    

    这将返回类似于图 12.23的输出:

    确认 MySQL 防火墙规则成功创建的输出

    图 12.23:监控 MySQL 防火墙规则的创建

    一旦你收到防火墙规则已成功配置的消息,你可以通过按Ctrl + C退出此命令。

  6. 让我们验证一下这些内容是否已经成功创建在 Azure 门户中。为此,首先在 Azure 搜索框中搜索 MySQL 服务器名称(在此示例中为 wp-helm-mysql),如图 12.24所示。点击服务器以查看详细信息:使用 Azure 搜索框搜索 MySQL 服务器

    图 12.24:在 Azure 门户中搜索 MySQL 服务器

  7. 这将带你到 MySQL 服务器的概览面板。在此面板中向下滚动并展开“可用资源”部分。在这里,你应该能看到 wordpress-db 已被创建,如图 12.25所示:通过 ASO 在 Azure 门户中创建的数据库

    图 12.25:通过 ASO 创建的数据库在 Azure 门户中的显示

  8. 在 MySQL 服务器面板中,点击左侧导航栏中的“连接安全性”以验证防火墙规则。你应该能在此面板上看到你通过 ASO 创建的防火墙规则,如图 12.26所示:

通过 ASO 创建的防火墙规则已设置在 MySQL 服务器上

图 12.26:通过 ASO 创建的防火墙规则已设置在 MySQL 服务器上

这验证了您能够在 Azure 中创建一个具有数据库的 MySQL 服务器,并配置其防火墙设置。

在本节中,您使用 ASO 创建了一个 MySQL 服务器,以及该服务器上的一个数据库,然后最终配置了其防火墙。您能够使用 Kubernetes YAML 文件完成所有这些操作。ASO 将这些 YAML 文件转换为 Azure,并为您创建了资源。最后,您确认在 Azure 门户中一切都已创建和配置完成。

在下一节,您将使用此数据库来支持 WordPress 应用程序。

使用 MySQL 数据库创建应用程序

您现在拥有一个 MySQL 数据库。为了展示您可以使用此数据库来配置一个应用程序,您将使用 WordPress 应用程序。您可以使用 Helm 安装它,并在 Helm 配置中提供与您的数据库的连接信息:

  1. 首先,您需要连接到数据库服务器的连接信息。当您在集群上安装 ASO 时,您配置它使用 Key Vault 作为秘密存储而不是 Kubernetes 秘密。您需要此连接信息将 WordPress 连接到 Azure MySQL 数据库。在 Azure 搜索栏中搜索 Key Vaults,如图 12.27所示,点击 Key vaults,然后选择您在本章前面创建的密钥库:使用 Azure 搜索栏搜索 Key Vault

    图 12.27:在 Azure 门户中搜索密钥库

  2. 在结果窗格中,点击左侧导航中的 Secrets,然后点击秘密,如图 12.28所示。此秘密的命名遵循命名约定 <object type>-<Kubernetes namesapce>-<object name>Azure 门户中的 MySQL 秘密

    图 12.28:Azure 门户中的 MySQL 秘密

  3. 您将看到一个包含多个版本秘密的视图;点击当前版本,如图 12.29所示:密钥库中不同版本的秘密

    图 12.29:您的密钥库中不同的秘密版本

    现在,复制秘密的值,如图 12.30所示:

    点击“复制到剪贴板”按钮复制秘密的值

    图 12.30:将秘密的值复制到剪贴板

  4. 该秘密包含与您的数据库连接相关的多个信息,您将在 Helm 安装中需要这些信息。它包含完全限定的服务器名称、用户名和密码。秘密中的值是 Base64 编码的。为了更轻松地处理此秘密,提供了一个 shell 脚本,该脚本将为您提供所需的解码值。要运行此脚本,请使用以下命令:

    sh decode-secret.sh <secret value>
    

    示例如图 12.31所示:

    运行脚本以生成解码的秘密值

    图 12.31:解码秘密

  5. 您可以使用前一步输出的值配置 Helm,使用您的 Azure MySQL 数据库。以下 Helm 命令将设置 WordPress 在您的集群上运行,但会使用外部数据库:

    helm repo add bitnami https://charts.bitnami.com/bitnami
    helm install wp bitnami/wordpress \
      --set mariadb.enabled=false \ 
      --set externalDatabase.host='<decoded host value>' \
      --set externalDatabase.user='<decoded user value>' \
      --set externalDatabase.password='<decoded password value>' \
      --set externalDatabase.database='wordpress-db' \
      --set externalDatabase.port='3306'
    

    如您所见,使用此命令,您通过将mariadb.enabled值设置为false,禁用了 MariaDB 安装,然后提供了外部数据库的连接信息。

    要监控 WordPress 的设置,您可以使用以下命令:

    kubectl get pods -w
    

    这将需要几分钟才能完全设置,最终,您应该能看到 WordPress pod 处于运行状态并准备就绪,如图 12.32所示:

    显示 WordPress pod 正在运行状态的输出

    图 12.32:WordPress pod 处于运行状态

    一旦 pod 运行并准备就绪,您可以通过按Ctrl + C来停止此命令。如果您还记得第三章中的 WordPress 部署,即AKS 上的应用程序部署,在 WordPress 安装中曾有一个第二个 pod,它托管了一个 MariaDB 数据库。由于我们已将其替换为 Azure MySQL 数据库,因此这个 pod 不再存在。

  6. 现在,让我们最终连接到这个 WordPress 应用程序。您可以使用以下命令获取 WordPress 网站的公共 IP 地址:

    kubectl get service
    

    这将显示公共 IP,如图 12.33所示:

    从输出中获取 WordPress 网站的公共 IP

    图 12.33:获取 WordPress 网站的公共 IP

    在您的网页浏览器的地址栏中输入此 IP 地址并按Enter键。您应该能看到带有默认示例帖子的 WordPress 登录页面,如图 12.34所示:

    浏览到 WordPress 网站

    图 12.34:浏览到 WordPress 网站

    现在,您已经在 Kubernetes 上托管了一个功能完整的 WordPress 网站,数据库由 Azure MySQL 数据库支持。

  7. 本章的示例已结束。您创建了多个资源并安装了多个集群组件。让我们使用以下命令将它们从集群中清理掉:

    helm uninstall wp
    kubectl delete -f mysql-firewall.yaml
    kubectl delete -f mysql-database.yaml
    kubectl delete -f mysql-server.yaml
    kubectl delete -f rg.yaml
    helm uninstall aso -n azureoperator-system
    az aks pod-identity delete --resource-group rg-handsonaks \
      --cluster-name handsonaks --namespace azureoperator-system \
      --name aso-identity-binding
    kubectl delete namespace azureoperator-system
    kubectl delete -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml
    az group delete -n aso --yes
    

您已经成功将 Kubernetes 上的应用程序连接到 Azure 托管的 MySQL 数据库。您使用了 WordPress Helm 图表,并提供了自定义值来配置该 Helm 图表,使其连接到托管数据库。

总结

本章介绍了Azure 服务操作员ASO)。ASO 是一个开源项目,使得通过 Kubernetes 创建 Azure 服务成为可能。这样,您作为用户就无需在 Azure 门户或 CLI 和 Kubernetes 资源定义之间切换。

在本章中,您创建了一个新的 AKS 集群,然后在该集群上安装了 ASO。接着,您使用 ASO 在 Azure 上创建了一个 MySQL 数据库。您通过 Azure 门户验证了此数据库在 Azure 中的可用性。

最后,你在 Kubernetes 集群上创建了一个 WordPress 应用,并连接到外部数据库。你验证了应用程序正在运行并且可用,就像你在前面的章节中看到的那样。

在下一章,你将了解其他与 AKS 集成的 Azure 服务,即 Azure 安全中心和 Azure Defender for Kubernetes,这些服务用于监控集群的安全配置并减少威胁。

第十四章:13. Kubernetes 的 Azure 安全中心

Kubernetes 是一个非常强大的平台,拥有大量配置选项。正确配置你的工作负载并确保遵循最佳实践可能是困难的。你可以遵循行业基准,以获得如何安全部署工作负载的指南,例如 互联网安全中心CIS)为 Kubernetes 提供的基准:https://www.cisecurity.org/benchmark/kubernetes/。

Azure 安全中心是一个统一的基础设施安全管理平台。它为 Azure 中的资源以及混合工作负载提供持续的安全监控和警报。它为许多 Azure 资源提供保护,包括 Kubernetes 集群。这样,你可以确保你的工作负载配置安全并且受到保护。

Azure 安全中心提供两种类型的保护。首先,它监控你的资源配置,并将其与安全最佳实践进行比较,然后为你提供可操作的建议来改善你的安全状况。其次,它还通过评估你的工作负载并在发现潜在威胁时发出警报来进行威胁保护。这一威胁检测功能是 Azure 安全中心中的一个功能,名为 Azure Defender。

在监控 Kubernetes 工作负载时,Azure 安全中心可以同时监控你的集群配置以及在集群中运行的工作负载的配置。为了监控集群中工作负载的配置,Azure 安全中心使用微软 Azure Kubernetes 策略。这个免费的附加组件适用于 Azure Kubernetes 服务AKS),它将使 Azure 安全中心能够将你的工作负载配置与已知的最佳实践进行比较。

Azure Defender 还为 Kubernetes 提供特定的威胁检测能力。它监控 Kubernetes 审计日志、节点日志以及集群和工作负载配置的组合,以识别潜在的威胁。可以发现的威胁示例包括加密矿工、高权限角色的创建或暴露 Kubernetes 仪表板。

在本章中,你将启用 Azure 安全中心、Azure Kubernetes 策略以及 Azure Kubernetes 防御器,并监控几个示例应用程序以防范威胁。

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

  • Kubernetes 的 Azure 安全中心

  • Azure Defender for Kubernetes

  • 部署有问题的工作负载

  • 使用 Azure 安全得分分析配置

  • 使用 Azure Defender 中和威胁

我们首先来设置 Kubernetes 的 Azure 安全中心。

为 Kubernetes 设置 Azure 安全中心

本章将从为 Kubernetes 设置 Azure Security Center 开始。要启用 Azure Security Center for Kubernetes,您需要在集群上启用 Azure Policy for AKS。这将允许 Azure Security 监控您的工作负载配置。为了利用 Azure Defender 的威胁防护,您还需要在订阅中启用 Azure Defender for Kubernetes。

让我们开始吧。

  1. 在 Azure 搜索栏中查找您的 AKS 集群,如图 13.1所示:在 Azure 搜索栏中查找您的集群

    图 13.1:在 Azure 搜索栏中查找您的集群

  2. 现在,您将为 AKS 启用 Azure Policy。要启用此功能,请点击左侧的 Policies 按钮,在弹出的面板中,点击 Enable add-on,如图 13.2所示:为您的 AKS 集群启用 Azure Policy

    图 13.2:为 AKS 启用 Azure Policy

    启用插件需要几分钟时间来完成。稍后,您应该会看到一条消息,显示服务已经启用,如图 13.3所示:

    显示 Azure Policy 已为您的 AKS 集群启用的通知

    图 13.3:Azure Policy for AKS 已启用

  3. 这已经为 AKS 启用了 Azure Policy。接下来,您将启用 Azure Defender,以便从 Azure Security Center 获取威胁防护功能。为此,请在 Azure 门户的搜索栏中查找 security center,如图 13.4所示:在 Azure 搜索栏中查找安全中心

    图 13.4:在 Azure 门户的搜索栏中搜索安全中心

  4. 如果这是您第一次在订阅中访问 Azure Security Center,您会看到如图 13.5所示的消息。要启用 Azure Defender,请点击屏幕底部的 Upgrade:

为您的订阅启用 Azure Defender

图 13.5:升级到 Azure Defender

如果您不是第一次在订阅中访问 Azure Security Center,可能不会看到启用 Azure Defender 的消息。要启用它,请点击左侧的 Pricing & settings 并选择您的订阅,如图 13.6所示:

为您的订阅启用 Azure Defender

图 13.6:手动升级到 Azure Defender

在弹出的面板中,选择标题为 Azure Defender on 的框,并点击屏幕顶部的 Save 按钮以启用 Azure Defender。您也可以选择调节要启用/禁用 Azure Defender 的服务,如图 13.7所示:

为您的订阅选择 Azure Defender 计划

图 13.7:为您的订阅启用 Azure Defender

现在您已经启用了 Azure Security Center 和 Azure Defender,系统配置默认策略并开始检测需要最多 30 分钟时间。

在等待此配置生效期间,你将在你的集群上部署多个有问题的工作负载,这将在 Azure Defender 中触发警报。

部署有问题的工作负载

要触发 Azure 安全中心的推荐和威胁警报,你需要在你的集群上部署有问题的工作负载。在本节中,你将部署一些可能未按照最佳实践配置甚至包含潜在恶意软件(如加密货币挖矿程序)的工作负载到你的集群中。让我们看一下本章节代码示例中的有问题的工作负载示例:

  • crypto-miner.yaml: 此文件包含一个部署,将在你的集群上创建一个加密货币挖矿程序。

    1   apiVersion: apps/v1
    2   kind: Deployment
    3   metadata:
    4     name: crypto-miner
    5     labels:
    6       app: mining
    7   spec:
    8     replicas: 1
    9     selector:
    10      matchLabels:
    11        app: mining
    12    template:
    13      metadata:
    14        labels:
    15          app: mining
    16      spec:
    17        containers:
    18        - name: mining
    19          image: kannix/monero-miner:latest
    

这个文件是 Kubernetes 中的一个常规部署。正如你在第 19 行所见,此部署的容器镜像将是一个加密货币挖矿程序。

注意

确保在完成本章后立即停止运行加密货币挖矿程序,如使用 Azure Defender 中和解中立化威胁部分所述。没有必要让加密货币挖矿程序长时间运行超过本示例所需的时间。

  • escalation.yaml: 此文件包含一个允许容器特权提升的部署。这意味着容器中的进程可以访问主机操作系统。有些情况下,这是期望的行为,但通常不希望这样配置。

    1   apiVersion: apps/v1
    2   kind: Deployment
    3   metadata:
    4     name: escalation
    5     labels:
    6       app: nginx-escalation
    7   spec:
    8     replicas: 1
    9     selector:
    10      matchLabels:
    11        app: nginx-escalation
    12    template:
    13      metadata:
    14        labels:
    15          app: nginx-escalation
    16      spec:
    17        containers:
    18        - name: nginx-escalation
    19          image: nginx:alpine
    20          securityContext:
    21            allowPrivilegeEscalation: true
    

正如在前面的代码示例中所见,在第 20–21 行,你使用 securityContext 配置了容器的安全上下文。你在第 21 行允许提权。

  • host-volume.yaml: 此文件包含一个在容器中挂载主机目录的部署。这不被推荐,因为这样容器可以访问主机。

    1   apiVersion: apps/v1
    2   kind: Deployment
    3   metadata:
    4     name: host-volume
    5     labels:
    6       app: nginx-host-volume
    7   spec:
    8     replicas: 1
    9     selector:
    10      matchLabels:
    11        app: nginx-host-volume
    12    template:
    13      metadata:
    14        labels:
    15          app: nginx-host-volume
    16      spec:
    17        containers:
    18        - name: nginx-host-volume
    19          image: nginx:alpine
    20          volumeMounts:
    21          - mountPath: /test-pd
    22            name: test-volume
    23            readOnly: true
    24        volumes:
    25        - name: test-volume
    26          hostPath:
    27            # Directory on host
    28            path: /tmp
    

这个代码示例包含一个 volumeMount 字段和一个卷。正如你在第 24–28 行所见,该卷使用 hostPath,意味着它在运行容器的节点上挂载一个卷。

  • role.yaml: 具有非常广泛权限的角色。建议在 Kubernetes 中采用最小特权原则来处理角色,以确保权限受到严格控制。具有广泛权限的角色首先是一个糟糕的配置,更糟的是,可能是集群受到威胁的迹象。

    1   apiVersion: rbac.authorization.k8s.io/v1
    2   kind: ClusterRole
    3   metadata:
    4     name: super-admin
    5   rules:
    6   - apiGroups: ["*" ]
    7     resources: ["*"]
    8     verbs: ["*"]
    

这个 ClusterRole 实例赋予了非常广泛的权限,正如你在第 6–8 行所见。这种配置将任何被分配此角色的人赋予 Kubernetes 中所有 API 的所有资源的所有权限。

这些代码示例中的任何部署都不包含资源请求和限制。正如第三章在 AKS 上部署应用中所解释的那样,建议配置资源请求和限制,因为这些可以防止工作负载消耗过多资源。

最后,你还将把 Kubernetes 仪表盘部署到公共服务上。这也是强烈不推荐的,因为这可能无意中让攻击者访问你的集群。你将看到 Azure Defender 如何检测到这一点。

现在开始部署这些文件。

  1. 在 Azure 门户中打开云 shell 并导航到本章的代码示例。

  2. 到达后,执行以下命令来创建有问题的工作负载。

    kubectl create -f crypto-miner.yaml
    kubectl create -f escalation.yaml
    kubectl create -f host-volume.yaml
    kubectl create -f role.yaml
    

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

    创建有问题的工作负载以触发 Azure 安全中心中的推荐和威胁警报

    图 13.8:创建有问题的工作负载

  3. 现在,使用以下命令部署 Kubernetes 仪表盘:

    kubectl apply -f `https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml`
    

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

    部署 Kubernetes 仪表盘

    图 13.9:创建 Kubernetes 仪表盘

  4. 默认情况下,Kubernetes 仪表盘不会通过负载均衡器暴露。这也是推荐的配置,因为仪表盘可以广泛访问你的集群。然而,在本章中,你将创建这种不推荐的配置,以触发 Azure Defender 中的安全警报。要将负载均衡器添加到 Kubernetes 仪表盘,请使用以下命令:

    kubectl patch service \
      kubernetes-dashboard -n kubernetes-dashboard \
      -p '{"spec": {"type": "LoadBalancer"}}'
    

    这将修补服务并将其转换为 LoadBalancer 类型的服务。使用以下命令验证此修补是否成功,并获取服务的公共 IP 地址:

    kubectl get service -n kubernetes-dashboard
    

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

    向 Kubernetes-dashboard 服务添加负载均衡器并获取其公共 IP

    图 13.10:获取 kubernetes-dashboard 服务的公共 IP

  5. 验证你是否可以通过浏览器访问此服务,方法是浏览到 https://<public IP>。根据你的浏览器配置,你可能会遇到证书错误,可以通过选择“继续访问 (不安全)”来绕过此错误,如图 13.11所示:

导航到 Kubernetes 仪表盘服务并验证连接是否不安全

图 13.11:Kubernetes 仪表盘服务的证书安全警告

一旦继续进入仪表盘,你应该看到一个登录屏幕,如图 13.12所示:

Kubernetes 仪表盘登录

图 13.12:暴露的 Kubernetes 仪表盘

你在这里不会登录仪表盘,但如果你希望探索其功能,请参考 Kubernetes 文档,网址为 kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/

你现在在集群中运行着五个有问题的工作负载。其中一些会在 Azure 安全中心中引发配置警告;另一些甚至会触发安全警报。你将在本章的接下来的两节中探索这些问题。

使用 Azure Secure Score 分析配置

在上一节中,你创建了几个故意配置错误的工作负载。在这一节中,你将回顾与这些工作负载相关的 Azure 安全中心推荐。

注意

在工作负载创建后,可能需要最多 30 分钟时间,推荐和警报才会显示出来。

  1. 在你创建了有问题的工作负载后,你将会在 Azure 安全中心中收到安全推荐。首先,在 Azure 安全中心的左侧导航中点击“安全评分”。这将显示一个类似于图 13.13的面板:在创建有问题的工作负载后查看 Azure 安全中心中的安全评分

    图 13.13:Azure 安全中心中的安全评分

    这里显示的是你环境的安全态势概述。在图 13.13所示的示例中,你可以看到整体安全评分为 32%。如果你管理多个 Azure 订阅,这个视图可以为你提供一个快速的全局安全配置视图。

  2. 让我们通过点击图 13.13底部的“Azure 订阅 1”订阅,深入了解 Kubernetes 集群的配置。这将带你进入一个与图 13.14类似的面板:查看你的 Azure 订阅的安全评分和安全推荐

    图 13.14:订阅的安全评分详情

    这个视图包含了有关该订阅的安全评分的更多细节。它再次向你展示安全评分、推荐状态和资源健康状况。屏幕底部包含了有关具体推荐的更多信息。

  3. 调整此屏幕,以便深入了解 Kubernetes 推荐。为此,请在屏幕右侧禁用“按控件分组”选项,并将资源类型过滤器设置为“托管集群”,如图 13.15所示:Azure 提供 Kubernetes 集群的安全推荐并显示资源健康状况

    图 13.15:Azure 安全中心中的 Kubernetes 推荐

    你现在正查看 Azure 安全中心推荐的 Kubernetes 安全推荐列表。该列表内容过于详细,无法在本章中完全涵盖,但如果你想了解每个推荐的更多细节,请参考 AKS 文档以获取更详细的描述:docs.microsoft.com/azure/aks/policy-reference

  4. 然而,让我们探索一些由你之前创建的有问题的工作负载导致的推荐。从点击名为“应该避免具有特权提升的容器”推荐开始。这将带你进入一个与图 13.16类似的视图:探索“应该避免具有特权提升的容器”推荐

    图 13.16:“应该避免具有特权提升的容器”推荐详情

    如你所见,这个推荐包含了对推荐项本身的描述,以及一系列需要遵循的修复步骤。它还展示了受影响的资源,在本例中是 AKS 集群。如果你点击集群,甚至可以看到更多关于问题工作负载的细节,如图 13.17所示:

    点击推荐页面上的集群将为你提供更多关于受影响组件的细节

    图 13.17:受权限提升推荐影响的 Pods

    在这种情况下,你创建的每个 Pod 都触发了这个推荐,而不仅仅是允许权限提升的那个。这表明 Kubernetes 默认允许这种权限提升,因此你应该在所有部署中实施这一保护措施。这也展示了像 Azure 安全中心这样的安全监控解决方案的优势,用于监控默认配置可能带来的潜在副作用。

  5. 让我们应用建议的修复措施来解决这个问题。为了解决权限提升问题,你需要配置容器的安全上下文,以不再允许权限提升。这可以通过使用以下命令更新每个部署来实现:

    kubectl patch deployment crypto-miner -p '
      {
        "spec": {
          "template": {
            "spec": {
              "containers": [
                {
                  "name": "mining",
                  "securityContext": {
                    "allowPrivilegeEscalation": false
                  }
                }
              ]
            }
          }
        }
      }
      '
    kubectl patch deployment escalation -p '
      {
        "spec": {
          "template": {
            "spec": {
              "containers": [
                {
                  "name": "nginx-escalation",
                  "securityContext": {
                    "allowPrivilegeEscalation": false
                  }
                }
              ]
            }
          }
        }
      }
      '
    kubectl patch deployment host-volume -p '
      {
        "spec": {
          "template": {
            "spec": {
              "containers": [
                {
                  "name": "nginx-host-volume",
                  "securityContext": {
                    "allowPrivilegeEscalation": false
                  }
                }
              ]
            }
          }
        }
      }
      '
    

    如命令中所示,你正在为每个部署打补丁。在每个补丁中,你配置了securityContext字段,并将allowPrivilegeEscalation字段设置为false

    在你应用补丁之后,Azure 安全中心可能需要最多 30 分钟来刷新安全推荐。时间过后,你的集群应该会显示为此推荐的健康资源,如图 13.18所示:

    遵循修复步骤后,集群现在被列为该条件的健康资源

    图 13.18:集群现在对权限提升推荐是健康的

  6. 让我们调查另一个推荐项,即名为“Pod HostPath 卷挂载的使用应限制在已知列表中,以限制受损容器对节点的访问”。点击此推荐项以查看更多细节,如图 13.19所示:调查名为“Pod HostPath 卷挂载的使用应限制在已知列表中,以限制受损容器对节点的访问”的推荐

    图 13.19:更多关于 HostPath 推荐的细节

    这个推荐向你展示了类似于之前的推荐信息。然而,触发这个推荐的策略可以进行调整,以允许访问某些HostPath。让我们探索如何编辑策略以允许此操作。

  7. 返回 Azure 安全中心主面板,在左侧点击“安全策略”。在弹出的面板中,点击你的订阅,然后点击显示在图 13.20中的 ASC 默认分配:导航到你的 Azure 订阅的安全策略

    图 13.20:ASC 默认策略分配

  8. 在弹出的窗格中,点击屏幕顶部的“参数”选项。查找参数列表中的“允许的主机路径”,并将该参数更改为以下内容:

    {  "paths": ["/tmp"]}
    

    结果如 图 13.21 所示:

    修改 ASC 默认分配的允许主机路径

    图 13.21:将路径添加到允许的主机路径

    要应用更改,请点击屏幕底部的“审查 + 保存”,然后在最后一个屏幕上点击“保存”:

    点击保存以确认修改

    图 13.22:点击保存以确认策略更改

    现在大约需要 30 分钟来刷新建议。30 分钟后,由于你已将 /tmp 路径设置为允许,此建议将不再有效。

  9. 这个列表中还有一个值得特别提到的建议。那就是 Kubernetes 服务管理 API 服务器应配置为限制访问。如果你记得的话,在 第十一章,《AKS 网络安全》中,你已经为你的集群配置了授权的 IP 范围。微软建议这样做,并且它也会出现在 Azure 安全中心的建议中。点击该建议以获取更多详细信息,如 图 13.23 所示:

调查 Kubernetes 服务管理 API 服务器应配置为限制访问的建议

图 13.23:详细说明应启用授权 IP 范围

如你所见,你会再次看到建议和修复步骤的描述。之所以特别强调这个建议,是因为它在 Azure 安全中心内提供了一个快速修复方法。要快速修复此建议,选择你的 AKS 集群,然后点击屏幕底部的“修复”按钮,如 图 13.24 所示:

使用快速修复按钮修复授权 IP 建议

图 13.24:修复授权 IP 建议

这将显示一个类似 图 13.25 的视图,你可以在其中输入需要授权访问的 IP 范围。

通过 Azure 安全中心设置授权的 IP 范围

图 13.25:通过 Azure 安全中心设置授权的 IP 范围

你可以从 Azure 安全中心内配置这个设置。由于在接下来的步骤和章节中你将使用 Cloud Shell,而 Cloud Shell 并没有固定的 IP,因此在本书中操作时不建议应用此修复方法。不过,值得一提的是,Azure 安全中心允许你直接在安全中心内修复某些配置建议。

你现在已经查看了 Azure Security Center 中的建议和安全分数。如果你想了解更多内容,请参考 Azure 文档,文档中包含了一个完整配置了所有建议的 YAML 部署示例:https://docs.microsoft.com/azure/security-center/kubernetes-workload-protections。

使用 Azure Defender 中和威胁

现在你已经通过 Azure Security Center 和安全分数探索了配置最佳实践,你将探索如何调查和处理安全警报和主动威胁。你创建的一些工作负载应该已经触发了安全警报,你可以在 Azure Defender 中调查这些警报。

具体来说,在部署违规工作负载部分,你创建了三个会触发 Azure Defender 安全警报的工作负载:

  • crypto-miner.yaml:通过部署此文件,你在集群中创建了一个加密矿工。这个加密矿工将在 Azure Defender 中生成两个安全警报,如本节所示。一个警报将通过监控 Kubernetes 集群本身生成,另一个警报将基于 DNS 流量生成。

  • role.yaml:这个文件包含一个具有非常广泛权限的集群级角色。这将在 Azure Defender 中生成一个安全警报,通知你存在风险。

  • Kubernetes 仪表板:你还创建了 Kubernetes 仪表板并公开了它。Azure Defender 也会基于此生成一个安全警报。

让我们详细探索每一个安全警报:

  1. 首先,在 Azure Security Center 中,点击左侧导航栏中的 Azure Defender。这将打开 Azure Security Center 中的 Azure Defender 面板,显示你的覆盖范围、安全警报和高级保护选项,如图 13.26所示。在本节中,你将重点关注生成的四个安全警报。Azure Defender 概览面板

    图 13.26:Azure Defender - 概览面板

  2. 要查看有关安全警报的更多详细信息,请点击屏幕中间的安全警报柱状图。这将带你到一个新面板,如图 13.27所示:点击安全警报柱状图可查看警报的严重性、状态和受影响的资源的详细信息

    图 13.27:Azure Defender 中的安全警报

    图 13.27所示,四个安全警报已被触发:一个是暴露的 Kubernetes 仪表板,两个是加密矿工,另一个是高权限角色。我们将更详细地探索每个警报。

  3. 我们首先探索检测到的暴露的 Kubernetes 仪表板警报。点击警报标题以获取更多详细信息。要查看所有细节,请点击结果面板中的“查看完整详情”,如图 13.28所示:点击查看完整详情后,将显示警报的详细信息

    图 13.28:获取警报的完整详情

    这将带你到一个新面板,如图 13.29所示:

    暴露的 Kubernetes 仪表板检测到的警报详细信息

    图 13.29:暴露的 Kubernetes 仪表板检测到的警报详细信息

    这显示了几个信息点。首先,它将此警报分类为高严重性,标记为活动状态,并标记首次出现的时间。接下来,你将看到警报的描述,在此案例中,它解释了仪表板不应公开暴露。它还显示了受影响的资源。最后,你会看到该攻击针对的是 MITRE ATT&CK®战术框架中的哪个阶段。MITRE ATT&CK®战术框架描述了网络攻击的多个阶段。有关 MITRE ATT&CK®战术的更多信息,请参阅attack.mitre.org/versions/v7/

    在屏幕的右侧,你将获得更多关于警报的详细信息。这些信息包括服务名称、命名空间、服务的端口、后端 Pod 上暴露的目标端口,以及受影响的 Azure 资源 ID 和订阅 ID。如果你点击屏幕底部的“下一步:采取行动 >>”按钮,你将进入一个新面板,如图 13.30所示:

    操作面板提供了关于如何缓解威胁和如何防止未来攻击的信息

    图 13.30:仪表板警报的安全建议

    在操作面板中,你可以获得关于如何缓解威胁和如何防止未来类似攻击的信息。请注意,“防止未来攻击”部分包含了你在前一节中查看的安全建议的链接。

  4. 让我们采取建议的行动,使用以下命令更新 Kubernetes 仪表板服务,使其不再是LoadBalancer类型。此命令将删除 Kubernetes 设置的通过负载均衡器暴露服务的nodePort,并将服务类型更改回仅在集群内部可用的ClusterIP类型。

    kubectl patch service \
      kubernetes-dashboard -n kubernetes-dashboard \
      -p '{
            "spec": {
              "ports": [
                {
                  "nodePort": null,
                  "port": 443
                }
              ],
              "type": "ClusterIP"
            }
          }'
    

    最后,你将看到可以选择触发自动响应或在未来抑制类似的警报,以防该警报是误报。

  5. 现在你已经缓解了威胁,可以关闭该警报。这样,使用相同订阅的其他人就不会看到相同的警报。要关闭警报,请点击屏幕左侧的状态,选择“已关闭”,然后点击“确定”,如图 13.31所示:在威胁已被缓解后关闭仪表板警报

    图 13.31:关闭仪表板警报

  6. 让我们继续查看下一个警报。通过点击屏幕顶部的 X 关闭仪表盘警报的详细窗格。现在让我们关注第一个“数字货币挖掘容器检测警报”。选择该警报并像之前一样点击“查看完整详情”。这将带你进入一个与图 13.32相似的窗格:调查数字货币挖掘容器检测警报

    图 13.32:数字货币挖掘容器检测警报的详情

    这个视图包含了与上一个警报类似的详细信息。正如你所看到的,这个警报属于 MITRE ATT&CK®策略框架中的“执行”阶段。在窗格的右侧,你现在可以看到受影响容器的名称、它使用的镜像、它的命名空间以及受影响 pod 的名称。

    如果你点击屏幕底部的“Next: Take Action >>”按钮,你将进入该警报的“Take action”视图,如图 13.33所示:

    Take action 窗格提供有关如何缓解威胁和如何防止未来攻击的信息

    图 13.33:数字货币挖掘容器检测警报的安全建议

    在这里,你再次看到与上一个警报类似的详细信息。在“缓解威胁”部分,你会看到如何缓解当前威胁的不同描述。暂时不要采取任何缓解措施,因为你还需要查看与加密矿工相关的另一个警报。

  7. 要查看该警报,首先通过点击屏幕顶部的 X 关闭第一个加密矿工警报的详细窗格。现在选择第二个警报,名为“数字货币挖掘活动(预览)”。这实际上不是一个 Kubernetes 警报,而是一个基于 DNS 的警报,正如图 13.34所示:

    注意

    kubectl delete -f crypto-miner.yaml
    
  8. 这将解决警报。要真正标记为已解决,你可以在 Azure 门户中取消警报。为此,点击屏幕左侧的状态,选择“已取消”状态,然后点击“确定”,如图 13.36所示:在威胁缓解后取消数字货币挖掘活动(预览)警报

    图 13.36:取消数字货币 DNS 警报

  9. 在安全警报窗格中,点击最后一个警报,名为“检测到新的高权限角色”,然后在出现的窗格中点击“查看完整详情”。这将带你进入一个与图 13.37相似的窗格:

探索新的高权限角色检测警报

图 13.37:新的高权限角色检测警报

这是一个低严重性的警报。与之前的警报一样,你会看到描述、受影响的资源以及 MITRE ATT&CK®策略框架中的阶段,在这种情况下是“持久性”阶段。这意味着攻击者可能利用此潜在攻击获得对环境的持久访问权限。

在右侧,你还可以看到警报的详细信息,包括角色名称、命名空间(在本例中是整个集群,因为这是一个ClusterRole)以及该角色所授予的访问规则。如果你点击“下一步:采取行动 >>”按钮,你将看到有关缓解措施的更多信息,如图 13.38所示:

操作面板提供有关如何缓解威胁和防止未来攻击的信息

图 13.38:新的高权限警报上的安全建议

如你所见,Azure 建议你查看警报中的角色并检查与该角色关联的任何角色绑定。同时建议授予比该角色中提供的开放权限更为严格的权限。让我们使用以下命令从集群中移除这个威胁:

kubectl delete -f role.yaml

这将从集群中删除该角色。你还可以通过点击屏幕左侧的状态,选择“已取消”状态,并点击确定来取消此警报,如图 13.39所示:

在威胁缓解后取消高权限角色警报

图 13.39:取消新的高权限警报

这涵盖了本章中你之前创建的资源所生成的所有警报。与警报相关的资源已经在修复过程中被删除,但让我们也删除本章中创建的其他资源:

kubectl delete -f escalation.yaml
kubectl delete -f host-volume.yaml
kubectl delete -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml

本章内容到此结束。

总结

本章中,你探索了 Azure 安全中心和 Azure Defender。Azure 安全中心是一个基础设施安全监控平台,提供安全配置监控以及潜在正在进行的威胁监控。为了监控 Kubernetes 集群中的工作负载,Azure 安全中心利用了 Azure Policy for AKS。

首先,你启用了 AKS 的 Azure Policy。然后你在订阅中启用了 Azure 安全中心和 Azure Defender。

然后你在集群中创建了五个有害的工作负载。其中一些在 Azure 安全中心触发了配置建议,另一些甚至在 Azure Defender 中触发了安全警报。你查看了四个安全警报,并按照推荐的缓解步骤解决了这些警报。

第十五章:14. 无服务器函数

由于可扩展性和减少管理开销,近年来无服务器计算和无服务器函数获得了巨大关注。Azure Functions、AWS Lambda 和 GCP Cloud Run 等云服务使用户可以轻松地将代码作为无服务器函数运行。

无服务器一词指的是任何不需要管理服务器的解决方案。无服务器函数是无服务器计算的一个子集,允许你按需运行代码作为函数。这意味着当有需求时,函数中的代码才会运行和执行。这种架构风格称为事件驱动架构。在事件驱动架构中,当发生事件时,事件消费者会被触发。在无服务器函数的情况下,事件消费者就是这些无服务器函数。事件可以是从队列中的一条消息到上传到存储的新对象,甚至是一个 HTTP 调用。

无服务器函数通常用于后台处理。一个常见的无服务器函数示例是创建上传到存储的图片的缩略图,如图 14.1所示。由于你无法预测会有多少图片被上传以及何时上传,因此很难规划传统基础设施以及需要多少服务器来处理这个过程。如果你将生成缩略图的过程实现为无服务器函数,那么每上传一张图片,该函数就会被调用。你无需规划函数的数量,因为每张新图片都会触发一个新的函数执行。

生成图片缩略图的无服务器函数示例架构

图 14.1:生成图片缩略图的无服务器函数示例架构

正如你在之前的示例中看到的,函数会自动扩展以应对增加或减少的需求。此外,每个函数可以独立于其他函数进行扩展。然而,这种自动扩展只是使用无服务器函数的一个好处。无服务器函数的另一个好处是开发的简便性。使用无服务器函数,你可以专注于编写代码,而无需处理底层基础设施。无服务器函数允许代码在无需担心管理服务器和中间件的情况下进行部署。最后,在公共云无服务器函数中,你按每次执行函数计费。这意味着你每次函数运行时都需要付费,而在函数未运行时,你不会为闲置时间收费。

公有云无服务器函数平台的流行促使了多个开源框架的出现,这些框架使用户能够在 Kubernetes 上创建无服务器函数。在本章中,你将学习如何直接使用 Azure Functions 的开源版本在 Azure Kubernetes Service (AKS) 上部署无服务器函数。你将从运行一个基于 HTTP 消息触发的简单函数开始。之后,你将安装一个 autoscaler 功能到你的集群中。你还将把 AKS 部署的应用程序与 Azure 存储队列集成。我们将涵盖以下主题:

  • 不同函数平台概览

  • 部署一个 HTTP 触发的函数

  • 部署队列触发的函数

本章将从探索 Kubernetes 上可用的各种函数平台开始。

各种函数平台

函数平台,如 Azure Functions、AWS Lambda 和 Google Cloud Functions,已经获得了巨大的普及。能够在无需管理服务器的情况下运行代码,并且几乎可以实现无限扩展,非常受欢迎。使用云服务商的函数实现的缺点是,你被锁定在该云服务商的基础设施和编程模型中。此外,你只能在公有云中运行函数,不能在自己的数据中心运行。

为了解决这些缺点,推出了多个开源的函数框架。现在有多个受欢迎的框架可以在 Kubernetes 上运行:

  • Knative (https://cloud.google.com/knative/): Knative 是一个由 Google 开发的、使用 Go 语言编写的无服务器平台。你可以将 Knative 函数完全托管在 Google Cloud 上运行,或者在你自己的 Kubernetes 集群上运行。

  • OpenFaaSCloud。该平台使用 Go 语言编写。

  • Kubeless

  • Fission.io (https://fission.io/): Fission 是一个由 Platform9 公司支持的无服务器框架。它使用 Go 语言编写,并且是 Kubernetes 原生的。它可以在任何 Kubernetes 集群上运行。

  • Apache OpenWhisk (https://openwhisk.apache.org/): OpenWhisk 是由 Apache 组织维护的开源分布式无服务器平台。它可以在 Kubernetes、Mesos 或 Docker Compose 上运行。主要使用 Scala 语言编写。

微软在其函数平台上采取了一种有趣的策略。微软将 Azure Functions 作为 Azure 上的托管服务进行运营,并且已将完整的解决方案开源,允许在任何系统上运行(https://github.com/Azure/azure-functions-host)。这也使得 Azure Functions 编程模型能够在 Kubernetes 上运行。

微软还与 Red Hat 合作发布了一个额外的开源项目,名为 Kubernetes 事件驱动自动扩展 (KEDA),旨在简化 Kubernetes 上函数的扩展操作。KEDA 是一个自定义的自动扩展器,允许 Kubernetes 上的部署从零个 Pod 扩展到任意数量的 Pod,这在 Kubernetes 的默认 水平 Pod 自动扩展器 (HPA) 中是无法实现的。从零扩展到一个 Pod 是非常重要的,因为这样应用程序可以开始处理事件,而扩展到零实例对于在集群中节省资源也非常有用。KEDA 还为 Kubernetes HPA 提供了额外的度量标准,以便基于来自集群外部的度量(例如队列中的消息数)做出扩展决策。

注意

我们在 第四章构建可扩展应用程序 中介绍并解释了 HPA。

在本章中,你将通过两个示例将 Azure Functions 部署到 Kubernetes 上:

  • 一个 HTTP 触发的函数(不使用 KEDA)

  • 一个队列触发的函数(使用 KEDA)

在开始这些函数之前,下一节将考虑这些部署所需的必要前提条件。

设置前提条件

在本节中,你将设置在 Kubernetes 集群上构建和运行函数所需的前提条件。你需要在 Azure 中设置一个 Azure 容器注册表 (ACR) 和一个用于开发函数的 虚拟机 (VM)。ACR 将用于存储包含你将要开发的函数的自定义容器镜像。你还将使用虚拟机来构建这些函数并创建 Docker 镜像,因为你不能通过 Azure Cloud Shell 来执行此操作。

容器镜像和容器注册表在 第一章容器与 Kubernetes 简介 中的 容器镜像 部分介绍过。容器镜像包含启动实际运行容器所需的所有软件。在本章中,你将构建包含函数的自定义容器镜像。你需要一个存储这些镜像的地方,以便 Kubernetes 可以拉取它们并按需扩展运行容器。你将使用 ACR 来实现这一点。ACR 是一个由 Azure 完全管理的私有容器注册表。

到目前为止,本书中的所有示例都在 Azure Cloud Shell 上运行。对于本章的示例,你需要一个单独的虚拟机,因为 Azure Cloud Shell 不允许你构建容器镜像。你将需要在 Azure 中创建一个新的虚拟机来执行这些任务。

让我们从创建一个 ACR 开始。

Azure 容器注册表

Azure Functions 在 Kubernetes 上需要一个镜像注册表来存储其容器镜像。在本节中,你将创建一个 ACR,并配置 Kubernetes 集群以访问该注册表:

  1. 在 Azure 搜索栏中,搜索 container registry,然后点击容器注册表,如 图 14.2 所示:通过 Azure 门户导航到容器注册表

    图 14.2:通过 Azure 门户导航到容器注册表服务

  2. 点击顶部的“添加”按钮以创建新的注册表。为了将本章中的资源组织在一起,创建一个新的资源组。为此,在资源组字段下点击“创建新资源组”以创建一个新的资源组,并将其命名为 Functions-KEDA,如 图 14.3 所示:创建新的资源组以创建注册表

    图 14.3:创建新的资源组

    提供创建注册表的详细信息。注册表名称需要全局唯一,因此可以考虑在注册表名称中添加你的首字母。建议在与集群相同的位置创建注册表。为了减少演示成本,可以将 SKU 更改为 Basic。选择底部的“审核 + 创建”按钮以创建注册表,如 图 14.4 所示:

    审核提供的详细信息并点击审核 + 创建以创建注册表

    图 14.4:提供创建注册表的详细信息

    在结果窗格中,点击“创建”按钮以创建注册表。

  3. 注册表创建完成后,打开 Cloud Shell,这样你就可以配置 AKS 集群以访问容器注册表。使用以下命令为 AKS 提供访问注册表的权限:

    az aks update -n handsonaks \
    -g rg-handsonaks --attach-acr <acrName>
    

    这将返回类似于 图 14.5 的输出。图像已被裁剪,仅显示输出的顶部部分:

为 AKS 提供注册表权限

图 14.5:允许 AKS 集群访问容器注册表

现在,你已经拥有了与 AKS 集成的 ACR。在下一部分,你将创建一个虚拟机,用于构建 Azure 函数。

创建虚拟机

在本节中,你将创建虚拟机并安装在该机器上运行 Azure 函数所需的工具:

  • Docker 运行时

  • Azure CLI

  • Azure 函数

  • Kubectl

    注意

    为确保一致的体验,你将在 Azure 上创建一个虚拟机,用于开发。如果你更喜欢在本地机器上运行示例,可以在本地安装所有必需的工具。

让我们开始创建虚拟机:

  1. 为了确保这个示例能在 Azure 试用订阅中运行,你需要将集群缩放到一个节点。你可以使用以下命令来做到这一点:

    az aks scale -n handsonaks -g rg-handsonaks --node-count 1
    
  2. 要对你将创建的虚拟机进行身份验证,你需要一组 SSH 密钥。如果你按照 第九章 中的示例操作,在 使用 AAD Pod 管理身份设置新集群 部分的 Azure Active Directory Pod 管理身份 中,你已经拥有了一组 SSH 密钥。要验证你是否拥有 SSH 密钥,运行以下命令:

    ls ~/.ssh
    

    这应该会显示 SSH 私钥(id_rsa)和公钥(id_rsa.pub)的存在,如 图 14.6 所示:

    验证存在的 SSH 密钥

    ssh-keygen
    

    系统会提示你输入位置和密码短语。保留默认位置,并输入一个空的密码短语。

  3. 现在,你将创建虚拟机。使用以下命令创建一个 Ubuntu 虚拟机:

    az vm create -g Functions-KEDA -n devMachine \
      --image UbuntuLTS --ssh-key-value ~/.ssh/id_rsa.pub \
      --admin-username handsonaks --size Standard_D1_v2
    
  4. 这需要几分钟时间完成。一旦虚拟机创建完成,Cloud Shell 应该会显示其公共 IP 地址,如 图 14.7 中所示:运行 az vm create 命令并获取虚拟机的公共 IP 地址

    ssh handsonaks@<public IP>
    

    系统将提示您是否信任该机器的身份。键入 yes 以确认。

  5. 您现在已连接到 Azure 上的新虚拟机。在这台机器上,我们将首先安装 Docker:

    sudo apt-get update
    sudo apt-get install docker.io -y
    sudo systemctl enable docker
    sudo systemctl start docker
    
  6. 为了使操作更加顺畅,将用户添加到 Docker 组。这将确保您能够在不使用 sudo 的情况下运行 Docker 命令:

    sudo usermod -aG docker handsonaks
    newgrp docker
    

    现在,您应该能够运行 hello-world 命令:

    docker run hello-world
    

    这将显示与 图 14.8 类似的输出:

    验证 Docker 在虚拟机上运行

    图 14.8:验证 Docker 是否在虚拟机上运行

  7. 接下来,您将在此虚拟机上安装 Azure CLI。您可以使用以下命令安装 CLI:

    curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
    
  8. 通过登录来验证 CLI 是否成功安装:

    az login
    

    这将显示一个登录代码,您需要在 https://microsoft.com/devicelogin 输入该代码:

    登录到 Azure CLI

    az acr login -n <registryname>
    

    ACR 的凭证在 3 小时后过期。如果在演示过程中遇到以下错误,您可以使用以下命令重新登录 ACR:

    在身份验证机器时可能出现的错误

    图 14.10:未来可能出现的身份验证错误

  9. 接下来,您将在您的机器上安装 kubectl。Azure CLI 提供了一个安装 CLI 的快捷方式,您可以使用它来安装:

    sudo az aks install-cli
    

    让我们验证 kubectl 是否能连接到我们的集群。为此,我们首先获取凭证,然后执行 kubectl 命令:

    az aks get-credentials -n handsonaks -g rg-handsonaks
    kubectl get nodes
    
  10. 现在,您可以在此机器上安装 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-3 -y
    

    这将返回类似于 图 14.11 的输出:

安装 Azure Functions 核心工具

图 14.11:安装 Functions 核心工具

注意

如果您使用的是比 18.04 更新的 Ubuntu 版本,请确保通过更改第一行中的 URL 以匹配您的 Ubuntu 版本,来下载正确的 dpkg 包。

现在,您已具备开始在 Kubernetes 上使用函数的先决条件。您已经创建了一个 ACR 用于存储自定义容器镜像,并且拥有一台将用于创建和构建 Azure 函数的虚拟机。在下一部分,您将构建第一个函数,它是 HTTP 触发的。

创建一个 HTTP 触发的 Azure 函数

在这个第一个示例中,您将创建一个 HTTP 触发的 Azure 函数。这意味着您可以浏览到托管实际函数的页面:

  1. 首先,创建一个新目录并导航到该目录:

    mkdir http
    cd http
    
  2. 现在,您将使用以下命令初始化一个函数:

    func init --docker
    

    ––docker 参数指定您将把函数构建为 Docker 容器。这将导致生成一个 Dockerfile。选择 Python 语言,这是下图中的选项 3:

    创建 Python 函数

    图 14.12:创建 Python 函数

    这将创建使函数能够工作的所需文件。

  3. 接下来,你将创建实际的函数。请输入以下命令:

    func new
    

    这应该会产生如下输出。选择第八个选项,HTTP 触发器,并将函数命名为python-http

    使用所需选项创建 HTTP 触发的函数

    图 14.13:创建 HTTP 触发的函数

  4. 函数的代码存储在名为python-http的目录中。你不需要对这个函数进行代码更改。如果你想查看该函数的源代码,可以运行以下命令:

    cat python-http/__init__.py
    
  5. 你需要对函数的配置文件进行一次修改。默认情况下,函数需要经过认证的请求。为了本次演示,你将此设置更改为anonymous。通过执行以下命令使用vi命令进行修改:

    vi python-http/function.json
    

    第 5 行authLevel替换为anonymous。要进行此更改,请按I进入插入模式,然后删除function并用anonymous替换:

    修改配置文件,将 authLevelfunction 更改为 anonymous

    图 14.14:将 authLevel 函数更改为 anonymous

    按下Esc键,输入:wq!,然后按Enter键保存并退出vi

    注意

    你已将函数的认证要求更改为anonymous。这样做会使演示更易于执行。如果你计划将函数发布到生产环境,需要仔细考虑此设置,因为它控制了谁可以访问你的函数。

  6. 现在,你已准备好将函数部署到 AKS。你可以使用以下命令部署该函数:

    func kubernetes deploy --name python-http \
    --registry <registry name>.azurecr.io
    

    这将导致函数运行时执行几个步骤。首先,它会构建一个容器镜像,然后将该镜像推送到注册表,最后,它将函数部署到 Kubernetes:

    使用 func kubernetes deploy 命令将函数部署到 AKS

    图 14.15:将函数部署到 AKS

    你可以点击显示的 Invoke url URL 来访问你的函数。不过,在执行此操作之前,让我们先了解一下在集群上创建的内容。

  7. 为了创建这个函数,使用了一个常规的 Kubernetes 部署。要检查部署状态,你可以运行以下命令:

    kubectl get deployment
    

    这将展示你的部署,如图 14.16所示:

    检查部署情况

    图 14.16:部署详细信息

  8. 该过程还在 Kubernetes 集群上创建了一个服务。你可以获取已部署服务的公共 IP,并连接到它:

    kubectl get service
    

    这将展示服务及其公共 IP,如图 14.17所示。请注意,这个公共 IP 与步骤 4中输出的 IP 是相同的。

获取服务的公共 IP

图 14.17:获取服务的公共 IP

打开浏览器并访问http://<external-ip>/api/python-http?name=handsonaks。您应该看到一个网页,显示“Hello, handsonaks. This HTTP triggered function executed successfully.” 这在图 14.18中展示:

导航到外部 IP 将返回 HTTP 函数输出

图 14.18:HTTP 触发函数的输出

现在,您已经创建了一个带有 HTTP 触发器的函数。使用 HTTP 触发的函数在负载模式不可预测的情况下提供 HTTP API 时非常有用。在进入下一节之前,让我们清理一下这个部署:

kubectl delete deployment python-http-http
kubectl delete service python-http-http
kubectl delete secret python-http

在本节中,您使用 HTTP 触发器创建了一个示例函数。让我们进一步提升,将一个新函数与存储队列集成,并在下一节中设置 KEDA 自动扩缩容。

创建队列触发的函数

在上一节中,您创建了一个示例 HTTP 函数。在本节中,您将构建另一个示例,使用队列触发的函数。队列常用于在应用程序的不同组件之间传递消息。可以根据队列中的消息触发函数,从而对这些消息执行额外的处理。

在本节中,您将创建一个与 Azure 存储队列集成的函数,以处理事件。您还将配置 KEDA,以便在流量较低时允许自动扩缩容,达到零 Pods。

让我们开始在 Azure 中创建一个队列。

创建队列

在本节中,您将创建一个新的存储帐户,并在该存储帐户中创建一个新队列。您将在下一节创建队列触发的函数中将函数连接到该队列。

  1. 首先,创建一个新的存储帐户。在 Azure 搜索栏中搜索storage accounts并选择存储帐户:通过 Azure 门户导航到存储帐户服务

    图 14.19:通过 Azure 门户导航到存储帐户服务

  2. 点击顶部的 + 新建按钮,创建一个新的存储帐户。提供创建存储帐户的详细信息。存储帐户名称必须是全局唯一的,因此可以考虑添加您的首字母。建议将存储帐户创建在与您的 AKS 集群相同的区域。最后,为了节省成本,建议将复制设置降级为本地冗余存储(LRS),如图 14.20所示:提供资源组和存储帐户详细信息以创建存储帐户

    图 14.20:提供详细信息以创建存储帐户

    准备好后,点击底部的“审核 + 创建”按钮。在弹出的屏幕中,选择“创建”开始创建过程。

  3. 创建存储账户大约需要一分钟。一旦创建完成,点击“转到资源”按钮打开该账户。在存储账户面板中,选择左侧导航栏中的“访问密钥”,点击“显示密钥”,并复制主连接字符串,如图 14.21所示。暂时记下这个字符串:访问密钥并复制连接字符串

    图 14.21:复制主连接字符串

    注意

    对于生产用例,不建议使用访问密钥连接到 Azure 存储。任何拥有该访问密钥的用户都可以完全访问存储账户,并可以读取和删除其中的所有文件。建议生成共享访问签名SAS)令牌来连接到存储,或使用 Azure AD 集成的安全性。要了解更多关于 SAS 令牌身份验证的信息,请参阅 https://docs.microsoft.com/rest/api/storageservices/delegate-access-with-shared-access-signature。要了解更多关于 Azure AD 身份验证到 Azure 存储的信息,请参阅 https://docs.microsoft.com/rest/api/storageservices/authorize-with-azure-active-directory。

  4. 最后一步是创建我们的队列存储账户。在左侧导航栏中查找queue,点击+ Queue 按钮添加一个队列,并为其提供名称。为了跟随本演示,使用function作为队列名称:

创建队列服务

图 14.22:创建一个新队列

你现在已经在 Azure 中创建了一个存储账户,并且拥有了它的连接字符串。你还在该存储账户中创建了一个队列。在下一部分,你将创建一个函数来消费队列中的消息。

创建一个队列触发的函数

在上一部分中,你已经在 Azure 中创建了一个队列。在本节中,你将创建一个新的函数来监视此队列并从队列中删除消息。你需要使用该队列的连接字符串来配置此函数:

  1. 在虚拟机中,首先创建一个新目录并导航到该目录:

    cd ..
    mkdir js-queue
    cd js-queue
    
  2. 现在我们可以创建函数了。我们将从初始化开始:

    func init --docker
    

    现在,它将询问你两个问题。对于运行时,选择 node(选项 2),对于语言,选择 JavaScript(选项 1)。这应该会显示在图 14.23中所示的输出:

    创建一个新的 JavaScript 类型函数

    func new
    

    这将要求你选择一个触发器。选择 Azure 队列存储触发器(选项 10)。为新的函数命名为js-queue。这应该会显示在图 14.24中所示的输出:

    创建一个使用 Azure 队列存储触发器的新函数

    图 14.24:创建一个队列触发的函数

  3. 现在,你需要进行一些配置更改。你需要为你创建的函数提供连接字符串到 Azure 存储,并提供队列名称。首先,打开local.settings.json文件来配置存储的连接字符串:

    vi local.settings.json
    

    按照以下步骤进行更改:

    • I 进入插入模式。

    • AzureWebJobsStorage 的连接字符串替换为您之前复制的连接字符串,并在该行末尾添加逗号。

    • 添加一行并在该行中添加以下文本:

    "QueueConnString": "<your connection string>"
    

    结果应类似于 图 14.25

    编辑 local.settings.json 文件中的队列连接字符串

    图 14.25:编辑 local.settings.json 文件

    • Esc 键保存并关闭文件,输入 :wq!,然后按 Enter
  4. 接下来需要编辑的文件是函数的配置文件。在此,您将引用之前的连接字符串,并提供我们在 创建队列 部分中选择的队列名称。为此,请使用以下命令:

    vi js-queue/function.json
    

    按照以下步骤进行更改:

    • I 进入插入模式。

    • 将队列名称更改为您创建的队列的名称(function)。

    • 接下来,将 QueueConnString 添加到 connection 字段中。

    您的配置现在应该类似于 图 14.26

    编辑 js-queue/function.json 文件

    图 14.26:编辑 js-queue/function.json 文件

    • Esc 键保存并关闭文件,输入 :wq!,然后按 Enter
  5. 现在,您已准备好将函数发布到 Kubernetes。首先,您需要在 Kubernetes 集群上设置 KEDA:

    kubectl create ns keda
    func kubernetes install --keda --namespace keda
    

    这应返回类似于 图 14.27 的输出:

    在 Kubernetes 集群上设置 KEDA

    kubectl get pod -n keda
    

    这应返回类似于 图 14.28 的输出:

    确保 KEDA Pod 正在 keda 命名空间中运行

    图 14.28:验证 KEDA 安装是否成功

  6. 现在,您可以将函数部署到 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
    

    这将返回类似于 图 14.29 的输出:

    部署队列触发的函数

    kubectl get all
    

    这将展示您已部署的所有资源。如 图 14.30 所示,设置过程中创建了部署、ReplicaSet 和 HPA。在 HPA 中,您应该看到当前没有副本在运行:

    验证设置是否已创建部署、ReplicaSet 和 HPA

    图 14.30:验证设置创建的对象

  7. 现在,您将创建一条消息以触发 KEDA 并创建一个 Pod。要查看扩展事件,运行以下命令:

    kubectl get hpa -w
    
  8. 要在队列中创建一条消息,我们将使用 Azure 门户。打开您之前创建的存储中的队列,点击屏幕顶部的 + 添加消息按钮,创建一条测试消息,然后点击 OK。如 图 14.31 所示:

向队列添加消息

图 14.31:向队列添加消息

创建完这条消息后,查看你之前执行的命令的输出。可能需要几秒钟,但很快,你的 HPA 应该会扩展到一个副本。之后,它还应该会缩减回零副本:

KEDA 从 0 扩展到 1,再缩减回 0 副本

图 14.32:KEDA 从 0 扩展到 1,再缩减回 0 副本

这向你展示了 KEDA 如何使 Kubernetes HPA 在队列中有消息时从零扩展到一个 Pod,当这些消息被处理后,又从一个 Pod 缩减到零 Pod。

你现在已经创建了一个由队列中添加的消息触发的函数。你已经验证了,当你在队列中创建消息时,KEDA 将 Pod 从 0 扩展到 1,而当队列中没有消息时,它又会缩减回 0。在下一部分,你将执行扩展测试,创建多个消息到队列中,观察函数如何反应。

扩展测试功能

在上一部分,你看到了当队列中有一条消息时,函数是如何反应的。在这个示例中,你将向队列发送 1,000 条消息,看看 KEDA 是如何首先扩展函数,然后再缩减,并最终缩减回零的:

  1. 在当前的 Cloud Shell 中,使用以下命令观察 HPA:

    kubectl get hpa -w
    
  2. 要开始推送消息,你需要打开一个新的 Cloud Shell 会话。要打开新会话,在 Cloud Shell 中选择“Open new session”按钮:打开一个新的 Cloud Shell 实例

    pip install azure-storage-queue==12.1.5
    

    安装完毕后,你需要为这个脚本提供你的存储帐户连接字符串。为此,使用以下命令打开文件:

    code sendMessages.py
    

    编辑第 8 行中的存储连接字符串为你的连接字符串:

    在第 8 行粘贴你的存储帐户连接字符串

    图 14.34:在第 8 行粘贴你的存储帐户连接字符串

  3. 一旦你粘贴了连接字符串,你可以执行 Python 脚本并向队列发送 1,000 条消息:

    python sendMessages.py
    

    在消息发送的过程中,切换回之前的 Cloud Shell 实例,观察 KEDA 从 0 扩展到 1,然后观察 HPA 扩展到副本数。HPA 使用 KEDA 提供的度量数据来做出扩展决策。Kubernetes 默认情况下不知道 KEDA 提供给 HPA 的 Azure 存储队列中消息的数量。

    注意

    根据你集群中 KEDA 扩展应用的速度,你的部署可能无法扩展到图 14.29中显示的 15 个副本。

    一旦队列为空,KEDA 将再次缩减回零副本:

KEDA 将从 0 扩展到 1,而 HPA 将扩展到 15 个 Pod。当负载减少时,KEDA 将再次缩减到 0

图 14.35:KEDA 将从 0 扩展到 1,而 HPA 将扩展到 15 个 Pod

正如你在该命令输出中所看到的,部署首先从零扩展到一个副本,然后逐渐扩展到最多 15 个副本。当队列中没有更多消息时,部署又缩减回零副本。

这就结束了在 Kubernetes 上运行无服务器函数的示例。让我们确保清理已创建的对象。请在你创建的虚拟机中运行以下命令(最后一步会删除该虚拟机;如果你希望保留虚拟机,请不要运行最后一步):

kubectl delete secret js-queue
kubectl delete scaledobject js-queue
kubectl delete deployment js-queue
func kubernetes remove --namespace keda
az group delete -n Functions-KEDA  --yes

在本节中,你运行了一个由存储队列中的消息触发的函数,运行在 Kubernetes 上。你使用了一个名为 KEDA 的组件来根据队列消息的数量实现扩展。你看到了 KEDA 如何从 0 扩展到 1,然后再缩减回 0。你还看到了 HPA 如何使用 KEDA 提供的指标来扩展部署。

总结

在本章中,你在 Kubernetes 集群上部署了无服务器函数。为此,你首先创建了一个虚拟机和一个 ACR。

你通过部署一个使用 HTTP 触发器的函数启动了函数部署。使用 Azure Functions 核心工具来创建该函数并将其部署到 Kubernetes。

随后,你在 Kubernetes 集群上安装了一个名为 KEDA 的额外组件。KEDA 允许在 Kubernetes 中进行无服务器扩展。它允许部署从零个 Pod 到任意数量的 Pod,并且还向 HPA 提供了额外的指标。你使用了一个在 Azure 存储队列中消息触发的函数。

在本书的下一个也是最后一章中,你将学习如何使用 GitHub Actions 在 持续集成和持续交付 (CI/CD) 流水线中集成容器和 Kubernetes。

第十六章:15. AKS 的持续集成与持续部署

DevOps 是人、流程和工具的结合,旨在更快速、更频繁和更可靠地交付软件。在 DevOps 文化中包含了 持续集成和持续部署CI/CD)的实践。CI/CD 是一组通过一种或多种工具实施的实践,用于自动测试、构建和交付软件。

CI 阶段指的是持续测试和构建软件的实践。CI 阶段的结果是一个可部署的构件。这个构件可以是多种形式;例如,对于 Java 应用程序,它可能是一个 JAR 文件,对于基于容器的应用程序,它可能是一个容器镜像。

CD 阶段指的是持续发布软件的实践。在 CD 阶段,CI 生成的构件将被部署到多个环境中,通常是从测试环境到质量保证(QA)环境,再到预发布环境,最后到生产环境。

有多种工具可以实现 CI/CD,其中 GitHub Actions 就是一个这样的工具。GitHub Actions 是一个内置于 GitHub 的工作流自动化系统。使用 GitHub Actions,你可以构建、测试和部署用任何语言编写的应用程序到多种平台。它还允许你构建容器镜像并将应用程序部署到 Kubernetes 集群中,你将在本章中进行操作。

本章将具体覆盖以下主题:

  • 容器和 Kubernetes 的 CI/CD 流程

  • 设置 Azure 和 GitHub

  • 设置 CI 流水线

  • 设置 CD 流水线

让我们从探索容器和 Kubernetes 的 CI/CD 生命周期开始。

容器和 Kubernetes 的 CI/CD 流程

在开始构建流水线之前,了解容器和 Kubernetes 的典型 CI/CD 流程是很有帮助的。在本节中,将更深入地探讨 图 15.1 所示的高层次流程。如果你想更详细地了解 Kubernetes 的 CI/CD 和 DevOps,推荐你阅读微软提供的免费在线电子书:docs.microsoft.com/dotnet/architecture/containerized-lifecycle/

容器和 Kubernetes CI/CD 流程的示意图

图 15.1:容器和 Kubernetes 的 CI/CD 流程

这个过程从某人进行代码更改开始。代码更改可能意味着应用程序代码的更改、更改用于构建容器的 Dockerfile,或更改用于在集群中部署应用程序的 Kubernetes YAML 文件。

一旦代码更改完成,这些更改会被提交到源代码管理系统。通常,这会是一个 Git 仓库,但也有其他系统,比如 Subversion(SVN)。在 Git 仓库中,通常会有多个代码分支。分支使得多个个人和团队可以并行在同一个代码库上工作,而不互相干扰。一旦一个分支上的工作完成,它会与主分支(或主分支)合并。分支合并后,该分支的更改将与使用该代码库的其他人共享。

注意事项

分支是 Git 源代码管理系统的强大功能。管理代码库中分支的使用有多种方式。请参考 Scott Chacon 和 Ben Straub 的 Pro Git(Apress,2014)一书中的分支章节,深入了解此主题:https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell。

代码推送到源代码管理系统后,无论是在主分支还是功能分支,CI 流水线都可以被触发。在基于容器的应用程序中,这意味着代码被构建成容器镜像,镜像经过测试,如果测试通过,它会被推送到容器注册表中。根据分支的不同,你可能会包含不同的步骤和测试。例如,在功能分支上,你可能只构建并测试容器以验证代码是否有效,但不会将其推送到注册表中,而在主分支上,你可能会构建并测试容器,并将其推送到容器注册表中。

最终,可以触发 CD 流水线来部署或更新你的 Kubernetes 应用程序。通常,在 CD 流水线中,部署会经过不同的阶段。你可以先将更新后的应用程序部署到暂存环境,在那里你可以对应用程序进行自动化和手动测试,然后再将其推送到生产环境。

现在你已经了解了容器和 Kubernetes 的 CI/CD 流程,你可以开始构建本章的示例部分。让我们从设置 Azure 和 GitHub 开始。

设置 Azure 和 GitHub

在本节中,你将设置用于创建和运行流水线的基本基础设施。为了托管容器镜像,你需要一个容器注册表。你可以使用多个容器注册表,但在这里你将创建一个 Azure 容器注册表实例,因为它与 Azure Kubernetes 服务AKS)集成得很好。在创建容器注册表后,你需要将该容器注册表与 AKS 集群链接,并创建一个新的服务主体,接着你需要设置一个 GitHub 仓库来运行本章的示例部分。按照以下七个步骤完成此活动:

  1. 首先,创建一个新的容器注册表。在 Azure 搜索栏中,搜索 container registry 并点击“容器注册表”,如图 15.2所示:在 Azure 搜索栏中搜索容器注册表

    图 15.2:通过 Azure 门户导航到容器注册表服务

  2. 点击顶部的“创建”按钮来创建新的注册表。为了将本章的资源组织在一起,创建一个新的资源组。为此,点击“创建新建”以创建一个新的资源组,并命名为 rg-pipelines,如图 15.3所示:点击“创建新建”按钮以创建新资源组

    图 15.3:创建一个新的资源组

    提供创建注册表所需的详细信息。注册表名称需要是全球唯一的,因此建议在注册表名称中添加你的首字母。建议在与你的集群相同的位置创建注册表。为了优化演示的开销,你可以将 SKU 更改为 Basic。选择底部的“查看 + 创建”按钮来创建注册表,如图 15.4所示:

    点击“查看 + 创建”按钮以创建新的容器注册表

    图 15.4:创建新的容器注册表

    在弹出的面板中,点击“创建”按钮以创建注册表。

  3. 当注册表创建完成后,打开 Cloud Shell,以便配置你的 AKS 集群以获取对容器注册表的访问权限。使用以下命令授予 AKS 对注册表的权限:

    az aks update -n handsonaks \
    -g rg-handsonaks --attach-acr <acrName>
    

    这将返回一个类似于图 15.5的输出,已裁剪为只显示输出的顶部部分:

    配置 AKS 集群以获取对容器注册表的访问权限

    图 15.5:允许 AKS 集群访问容器注册表

  4. 接下来,你需要创建一个服务主体,GitHub Actions 将使用它来连接到你的订阅。你可以使用以下命令创建此服务主体:

    az ad sp create-for-rbac --name "cicd-pipeline" \
    --sdk-auth --role contributor
    

    你需要该命令的完整输出 JSON,如图 15.6所示,稍后在 GitHub 中使用。复制此输出:

    创建新的服务主体

    图 15.6:创建一个新的服务主体

  5. 这完成了 Azure 部分的设置。接下来,你需要登录 GitHub,fork 本书附带的仓库,并在该仓库中配置一个 secret。如果你还没有 GitHub 帐户,请通过 https://github.com/join 创建一个。如果你已有帐户,请通过 https://github.com/login 登录。

  6. 一旦你登录 GitHub,浏览到本书相关的仓库:https://github.com/PacktPublishing/Hands-On-Kubernetes-on-Azure-third-edition。通过点击屏幕右上角的“Fork”按钮,在你的帐户中创建一个该仓库的 fork,如图 15.7所示:Fork GitHub 仓库

    图 15.7:Fork GitHub 仓库

    fork 仓库会在您自己的 GitHub 账户中创建一个副本。这将允许您对仓库进行更改,就像在本章中构建管道时所做的那样。

  7. fork 仓库只需要几秒钟。一旦在您自己的账户中有了 fork,您将需要在此仓库中配置 Azure 秘密。首先点击您仓库右上角的设置,如 图 15.8: 所示。

点击 GitHub 仓库中的设置按钮

图 15.8: 点击 GitHub 仓库中的设置按钮

这将带您进入仓库的设置。在左侧,点击 Secrets,然后在结果屏幕上方点击 New repository secret 按钮,如 图 15.9: 所示。

创建新的仓库秘密

图 15.9: 创建新的仓库秘密

这将带您到创建新秘密的屏幕。将此秘密称为 AZURE_CREDENTIALS,作为秘密的值,粘贴 CLI 命令输出中的内容,该命令在本节的步骤 4 中显示,如 图 15.10: 所示。

设置新秘密的值

图 15.10: 设置新秘密的值

最后,在此屏幕底部点击 Add secret 以保存秘密。

现在您已经设置好了 Azure 和 GitHub 来开始构建您的管道。您已经创建了一个服务主体,GitHub 将用它与 Azure 进行交互,并且您创建了一个容器注册表,您的 CI 管道可以将镜像推送到其中,AKS 可以从中拉取镜像。现在让我们构建一个 CI 管道。

设置一个 CI 管道

您现在已经准备好构建一个 CI 管道了。作为本节演示的一部分,您将构建一个带有小型自定义网页的 nginx 容器。容器构建完成后,您将把 nginx 容器推送到之前在上一节创建的容器注册表中。接下来的 13 个步骤中逐步构建 CI 管道:

  1. 首先,打开 fork 后的 GitHub 仓库并打开 Chapter 15 文件夹。在该文件夹中,您会找到一些文件,包括 Dockerfileindex.html。这些文件用于构建自定义容器。在示例中,您将更改 index.html 以触发 GitHub 动作中的更改。让我们来看看 index.html 的内容:

    1   <html>
    2   <head>
    3       <title>Version 1</title>
    4   </head>
    5   <body>
    6       <h1>Version 1</h1>
    7   </body>
    8   </html>
    

    这是一个简单的 HTML 文件,具有标题和标题,都说 Version 1。在设置 CD 管道部分,您将被要求增加版本号。

    接下来,您还提供了一个 Dockerfile 文件。该文件的内容如下:

    1   FROM nginx:1.19.7-alpine
    2   COPY index.html /usr/share/nginx/html/index.html
    

    这个 Dockerfile 从一个nginx-alpine基础镜像开始。Nginx 是一个流行的开源 Web 服务器,Alpine 是一个轻量级操作系统,通常用于容器镜像。在第二行,您将本地的index.html文件复制到容器中,复制到nginx加载网页的位置。

    现在你已经了解了应用程序本身,接下来可以开始构建 CI 流水线。供你参考,CI 流水线的完整定义已作为pipeline-ci.yaml文件包含在本章的代码文件中,但接下来会逐步指导你如何构建这条流水线。

  2. 让我们从创建一个 GitHub Actions 工作流开始。在 GitHub 屏幕顶部,点击 "Actions",然后点击 "set up a workflow yourself" 链接,如图 15.11所示:点击 “Set up a workflow yourself” 按钮以创建新的 GitHub 操作

    图 15.11:创建新的 GitHub 操作

  3. 这将带你进入一个 GitHub 代码编辑器。首先,将流水线文件的名称改为 pipeline.yaml,并将第 3 行的名称改为 pipeline,如图 15.12所示:更改流水线名称

    图 15.12:更改流水线名称

  4. 接下来,你将关注工作流的触发条件。在本示例中,你只会使用主分支。然而,你并不希望工作流对每个代码更改都执行。你只希望当对 Chapter 15 文件夹中的流水线定义或代码进行更改时,工作流才会运行。为此,你可以设置以下代码来控制工作流的触发:

    4   # Controls when the action will run. 
    5   on:
    6     # Triggers the workflow on push or pull request events but only for the main branch
    7     push:
    8       branches: [ main ]
    9       paths: 
    10      - Chapter15/**
    11      - .github/workflows/pipeline.yaml  
    12    # Allows you to run this workflow manually from the Actions tab
    13    workflow_dispatch:
    

    这段代码配置的是以下内容:

    • Chapter15 目录以及 .github/workflows/ 目录中 pipeline.yaml 文件的更改将触发工作流的运行。

    • ACRNAME 变量设置为你创建的容器注册表的名称。通过使用变量,你可以避免在多个地方配置相同的值。

      这解释了流水线是如何触发的,以及如何配置变量;接下来让我们看看流水线中将会运行什么。

    • 在定义流水线中执行的命令之前,让我们先了解 GitHub Actions 工作流的结构,如图 15.13所示:

GitHub 操作结构的表示

图 15.13:GitHub Actions 工作流

一个 GitHub Actions 工作流由多个作业组成。每个作业可以包含多个步骤。作业默认并行运行,但可以配置为顺序运行。作业中的步骤将按顺序执行。作业中的每个步骤将包含作为流水线一部分实际执行的命令。一个步骤的例子可能是构建容器镜像。在工作流中有多种方式运行命令:你可以像在常规终端中一样直接运行 shell 命令,也可以运行 GitHub 社区提供的预构建操作。

作业和步骤是在所谓的运行器上运行的。默认情况下,工作流在托管的运行器上运行。这些托管的运行器运行在由 GitHub 设置和管理的基础设施上。你也可以选择在自托管运行器上运行作业和步骤。这样可以让你对运行器有更多的配置能力,例如,允许使用特殊硬件或安装特定软件。自托管的运行器可以是物理的、虚拟的、容器中的、本地的或云中的。

在本节中,你将运行来自社区的工作流步骤以及 shell 命令。有关社区提供的动作的概览,请访问 GitHub Marketplace:https://github.com/marketplace?type=actions。

在你正在构建的 CI 管道中,你需要执行以下步骤:

  1. 获取 GitHub 仓库到动作运行器,也称为检出你的仓库。

  2. 登录到 Azure CLI。

  3. 登录到 Azure 容器注册表。

  4. 构建一个容器镜像并将此容器镜像推送到 Azure 容器注册表。

让我们一步步构建管道。

  1. 在构建实际步骤之前,你需要配置作业和作业的配置。具体来说,对于这个示例,你可以使用以下配置:

    18  jobs:
    19    # This workflow contains a single job called "CI"
    20    CI:
    21      # The type of runner that the job will run on
    22      runs-on: ubuntu-latest
    

    你正在配置以下内容:

    • 目前是 CI。你稍后会添加 CD 作业。

    • ubuntu-latest

    这配置了 GitHub 运行器的步骤。现在,让我们开始构建各个步骤。

  2. 第一步是检查 Git 仓库。这意味着代码将在运行器中加载。可以使用以下几行代码来实现:

    25      steps:
    26        # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
    27        - name: Git checkout
    28          uses: actions/checkout@v2
    

    这里表示的第一行(第 25 行)是打开 steps 块和所有后续步骤的地方。第一步叫做 Git checkout第 27 行),指的是一个名为 actions/checkout@v2 的预构建动作。@v2 表示你正在使用该动作的第二个版本。

  3. 接下来,你需要登录 Azure CLI,然后使用 Azure CLI 登录到 Azure 容器注册表。为此,你将使用市场中的一个动作。你可以通过屏幕右侧的搜索栏找到市场中的项目,如图 15.14 所示: 在 GitHub Marketplace 中搜索 Azure 登录动作

    30        - name: az CLI login
    31          uses: azure/login@v1
    32          with:
    33            creds: ${{ secrets.AZURE_CREDENTIALS }}
    34
    35        - name: ACR login
    36          run: az acr login -n $ACRNAME
    

    第一步是在 GitHub Actions 运行器上登录 Azure CLI。为了登录 Azure CLI,它使用了你在上一节中配置的密钥。第二个任务执行一个 Azure CLI 命令,以登录 Azure 容器注册表。它使用你在第 14-15 行配置的变量。它将 login 命令作为常规 shell 命令执行。在接下来的步骤中,你将把镜像推送到此容器注册表。

  4. 接下来,你将构建容器镜像。虽然有多种方式可以做到这一点,但在这个示例中你将使用 docker/build-push-action

    39        - name: Build and push image
    40          uses: docker/build-push-action@v2
    41          with:
    42            context: ./Chapter15
    43            push: true
    44            tags: ${{ env.ACRNAME }}.azurecr.io/website/website:${{ github.run_number }}
    

    这一步将构建你的容器镜像并推送到注册表。你配置了在Chapter15文件夹中运行,因此 Dockerfile 中对index.html页面的引用保持有效。它将使用你的容器注册表的名称为镜像打标签,并且将 GitHub Actions 的运行号作为容器镜像的版本号。为了获取工作流的运行号,你使用了 GitHub 配置的默认环境变量之一。有关完整的列表,请参考 GitHub 文档:GitHub 文档

    注意

    在这个例子中,你使用工作流的运行号作为容器镜像的版本。为容器镜像打标签很重要,因为标签版本表示容器的版本。还有许多其他策略可以用来为容器镜像版本化。

    一个不推荐的策略是使用latest标签标记容器镜像,并在 Kubernetes 部署中使用该标签。latest标签是 Docker 在未提供标签时会自动添加到镜像的默认标签。使用latest标签的问题在于,如果容器注册表中的latest标签的镜像发生变化,Kubernetes 不会立即获取这个变化。在拥有本地latest标签镜像副本的节点上,Kubernetes 不会在超时过期之前拉取新镜像;但是,没有该镜像副本的节点会在需要运行此镜像的 Pod 时拉取更新版本。这可能导致在单个部署中运行不同版本的镜像,这是应当避免的。

  5. 现在你已经准备好保存并运行这个 GitHub Actions 工作流了。你可以点击“Start Commit”按钮保存工作流配置文件,然后通过点击“Commit new file”进行确认,正如图 15.16所示:保存 Action 配置文件

    图 15.16:保存 Action 配置文件

  6. 一旦文件被保存,工作流将触发运行。要查看工作流运行的日志,你可以点击屏幕顶部的“Actions”。这应该会显示一个类似于图 15.17的屏幕:查看 Actions 运行历史

    图 15.17:获取 Actions 运行历史

    点击顶部条目以获取更多关于你工作流运行的详情。这将带你到一个类似于图 15.18的屏幕:

    Action 运行的详细视图

    图 15.18:Action 运行的详细屏幕

    这显示了你的工作流详情,并告诉你在工作流中只有一个任务。点击 CI 查看该任务的日志。这将显示一个类似于图 15.19的屏幕:

    查看 CI 任务的日志

    图 15.19:CI 任务的日志

    在这个屏幕上,你可以看到工作流中每一步的输出日志。你可以通过点击每一步前面的箭头图标来展开该步骤的日志。

  7. 在这个示例中,你构建了一个容器镜像,并将其推送到 Azure 上的容器注册表。接下来,我们来验证这个镜像是否已经成功推送到注册表。为此,返回 Azure 门户,在搜索栏中输入container registry,如图 15.20 所示通过 Azure 搜索栏搜索容器注册表

    图 15.20:通过 Azure 门户导航到容器注册服务

    在结果页面中,点击你之前创建的注册表。接下来,点击左侧的 Repositories 选项,应该会显示出website/website仓库,如图 15.21 所示

    查看容器注册表中的网站/网站仓库

    图 15.21:显示容器注册表中的网站/网站仓库

  8. 如果点击website/website仓库的链接,你应该会看到你的容器镜像的镜像标签,如图 15.22 所示

查看容器镜像的镜像标签

图 15.22:容器镜像的镜像标签

如果你对比图 15.18图 15.22的输出,你会发现动作的运行号也是镜像的标签。在你的例子中,运行号和标签可能是 1。

现在,你已经构建了一个基本的 CI 流水线。当Chapter 15文件夹中的代码发生变化时,流水线会运行并构建一个新的容器镜像,该镜像将被推送到容器注册表。在下一节中,你将向流水线中添加一个 CD 任务,用于将镜像部署到 Kubernetes 中的部署。

设置 CD 流水线

你已经拥有一个包含 CI 任务的流水线,该任务会构建一个新的容器镜像。在本节中,你将向该流水线中添加一个 CD 任务,该任务会将更新后的容器镜像部署到 Kubernetes 中的部署。

为了简化应用部署,website文件夹中已提供应用程序的 Helm Chart。你可以通过部署 Helm Chart 来部署应用程序。通过 Helm Chart 部署时,你可以使用命令行覆盖 Helm 值。你在第十二章,将应用连接到 Azure 数据库中也做过类似操作,当时你配置了 WordPress 使用外部数据库。

在这个 CD 任务中,你需要执行以下步骤:

  1. 查看代码。

  2. 获取 AKS 凭证。

  3. 设置应用程序。

  4. (可选)获取服务的公共 IP。

让我们开始构建 CD 流水线。为了方便参考,完整的 CI 和 CD 流水线已经在pipeline-cicd.yaml文件中提供:

  1. 要向流水线中添加 CD 任务,你需要编辑pipeline.yaml文件。为此,在你自己的仓库中,点击屏幕顶部的“Code”按钮,然后进入.github/workflows文件夹。在该文件夹中,点击pipeline.yaml文件。文件打开后,点击右上角的笔图标,如图 15.23 所示点击笔图标编辑 pipeline.yaml 文件

    图 15.23:编辑 pipeline.yaml 文件

  2. 在文件底部,首先添加以下几行来定义 CD 作业:

    46    CD:
    47      runs-on: ubuntu-latest
    48      needs: CI
    49      steps:
    

    在这段代码中,你正在创建 CD 作业。该作业将再次在 ubuntu-latest 运行器上运行。在 第 48 行,你定义了该作业依赖于 CI 作业。这意味着该作业只有在 CI 作业完成之后才会开始,并且只有在 CI 作业成功完成的情况下才会运行。最后,第 49 行 打开了 steps 块,接下来你将在其中填写内容。

  3. 第一步将是 Git 检出。这将使用你在 CI 作业中使用的相同步骤:

    50        - name: Git checkout
    51          uses: actions/checkout@v2
    
  4. 接下来,你需要登录到 Azure CLI 并获取 AKS 凭据。你可以使用与 CI 作业中相同的方法来完成这一步,意味着你可以执行 Azure CLI 登录,然后在运行器上运行 az aks get-credentials 命令。不过,也有一个单独的 GitHub 动作可以为 AKS 完成这项工作:

    53        - name: Azure Kubernetes set context
    54          uses: Azure/aks-set-context@v1
    55          with:
    56            creds: ${{ secrets.AZURE_CREDENTIALS }}
    57            resource-group: rg-handsonaks
    58            cluster-name: handsonaks
    

    这一步使用了 Microsoft 的 Azure/aks-set-context 动作。你使用你创建的 Azure 凭证密钥对其进行配置,然后定义你要使用的资源组和集群名称。这将配置 GitHub 动作运行器使用这些 AKS 凭证。

  5. 现在,你可以在集群上创建应用程序。如本节开头所提到的,你将使用本章中的 website 文件夹中创建的 Helm Chart 部署该应用程序。要在集群上部署这个 Helm Chart,你可以使用以下代码:

    60        - name: Helm upgrade
    61          run: |
    62            helm upgrade website Chapter15/website --install \
    63              --set image.repository=$ACRNAME.azurecr.io/website/website \
    64              --set image.tag=${{ github.run_number }}
    

    这段代码执行一个 Helm upgrade 命令。第一个参数(website)指的是 Helm 发布的名称。第二个参数(Chapter15/website)指的是 Helm Chart 的位置。--install 参数将 Helm 配置为,如果 Chart 尚未安装,它会自动安装。这将在你第一次运行该动作时发生。

    在接下来的两行中,你设置了 Helm 值。你将镜像仓库设置为容器注册表中的 website/website 仓库,并将标签设置为操作的运行编号。这是你在 CI 步骤中用来标记镜像的相同值。

  6. 最后,你可以在工作流中包括一个可选步骤。这是获取将被创建并用于服务你网站的服务的公网 IP 地址。这是可选的,因为你可以在 Azure Cloud Shell 中使用 kubectl 获取该 IP 地址,但为了方便起见,这里为你提供了该步骤:

    66        - name: Get service IP
    67          run: |
    68            PUBLICIP=""
    69            while [ -z $PUBLICIP ]; do
    70              echo "Waiting for public IP..."
    71              PUBLICIP=$(kubectl get service website -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    72              [ -z "$PUBLICIP" ] && sleep 10
    73            done
    74            echo $PUBLICIP
    

    这段代码将运行一个小的 Bash 脚本。虽然公网 IP 尚未设置,它将不断使用 kubectl 从服务中获取公网 IP。一旦公网 IP 设置好,公网 IP 将会显示在 GitHub Actions 日志中。

  7. 现在,你已经准备好保存更新后的流水线并第一次运行它。要保存流水线,点击屏幕右上角的“开始提交”按钮,并在弹出的窗口中点击“提交更改”,如 图 15.24 所示: 保存更新后的流水线

    图 15.24:流水线工作流

  8. 一旦你提交了更改到 GitHub,工作流将被触发运行。为了跟踪部署情况,点击屏幕顶部的 Actions。点击这里的顶部条目查看运行的详细信息。最初,输出将类似于图 15.25:操作运行时的详细输出

    图 15.25:操作运行时的详细输出

    如你所见,现在你可以访问该工作流中两个作业,CI 作业和 CD 作业。当 CI 作业正在运行时,CD 作业的日志将不可用。一旦 CI 作业成功完成,你将能够访问 CD 作业的日志。等待几秒钟,直到屏幕显示如图 15.26所示,表明工作流已成功完成:

    两个作业完成后操作运行的详细输出

    图 15.26:两个作业完成后操作运行的详细输出

  9. 现在,点击 CD 作业查看该作业的日志。点击“获取服务 IP”旁边的箭头,查看创建的服务的公共 IP,如图 15.27:所示。CD 作业日志,显示服务的公共 IP 地址

    图 15.27:CD 作业日志,显示服务的公共 IP 地址

    在你的网页浏览器中打开一个新标签页,访问你的网站。你应该能看到类似于图 15.28:的输出。

    网站运行版本 1

    图 15.28:网站运行版本 1

  10. 现在让我们通过修改index.html文件来测试端到端的管道。为此,在 GitHub 中,点击屏幕顶部的 Code,打开Chapter15,然后点击index.html文件。在打开的窗口中,点击右上角的铅笔图标,如图 15.29:所示。点击铅笔图标编辑 index.html 文件

    图 15.29:点击铅笔图标编辑 index.html 文件

  11. 现在你可以编辑文件。将网站的版本更改为version 2(或进行其他你可能想要的更改),然后滚动到屏幕底部以保存更改。点击提交更改按钮将更改提交到 GitHub,如图 15.30:所示。保存对 index.html 文件的更改

    图 15.30:保存对 index.html 文件的更改

  12. 这将触发工作流运行。它将依次运行 CI 和 CD 作业。这意味着一个新容器将被构建,并更新index.html文件。你可以像之前一样,通过点击屏幕顶部的 Actions 并点击顶部的运行条目来跟踪工作流的状态。等待作业完成,如图 15.31:所示。更新 index.html 后的操作运行

    图 15.31:更新 index.html 后的操作运行

  13. 如果你现在返回到 第 9 步 输出的 IP 地址,你应该会看到更新后的网页,显示版本 2,如 图 15.32: 所示。

网页已更新为版本 2

图 15.32:网页已更新为版本 2

这表明流水线成功执行,并将你的代码更改部署到生产环境。

注意

在此示例中,你直接更新了生产版本的网站,而没有任何审批流程。GitHub Actions 还允许你配置手动审批,以便在将更改推广到生产环境之前进行测试。要配置手动审批,你可以使用 GitHub Actions 中的环境功能。有关更多信息,请参阅 https://docs.github.com/en/actions/reference/environments。

本示例的 CI 和 CD 使用 GitHub Actions 也到此结束。让我们确保清理为本章创建的资源。在 Cloud Shell 中执行以下命令:

helm uninstall website
az group delete -n rg-pipelines --yes

由于这也标志着本书示例的结束,如果你不再需要集群,你现在也可以删除它。如果你想这么做,可以使用以下命令删除集群:

az group delete -n rg-handsonaks --yes

通过这种方式,你可以确保在完成本书中的示例后,如果不再使用这些资源,你就不需要为它们支付费用。

总结

现在,你已经成功创建了一个 CI/CD 流水线用于 Kubernetes 集群。CI 是频繁构建和测试软件的过程,而 CD 是定期部署软件的实践。

在本章中,你使用了 GitHub Actions 作为平台来构建 CI/CD 流水线。你首先构建了 CI 流水线。在该流水线中,你构建了一个容器镜像并将其推送到容器注册中心。

最后,你还添加了一个 CD 流水线,将容器镜像部署到 Kubernetes 集群。你能够通过修改网页代码来验证,当代码更改时,流水线被触发并将代码更改推送到集群中。

你在本章中构建的 CI/CD 流水线是一个入门流水线,为更强大的 CI/CD 流水线奠定基础,后者可用于将应用程序部署到生产环境。在将其投入生产之前,你应该考虑向流水线添加更多的测试,并将其与不同的分支进行集成。

最后的思考

本章也标志着本书的结束。在本书的过程中,你通过一系列实践示例学习了如何使用 AKS。

本书从基础知识开始,你学习了容器和 Kubernetes,并创建了一个 AKS 集群。

下一节聚焦于在 AKS 上部署应用程序。你学习了将应用程序部署到 AKS 的不同方式,如何扩展应用程序,如何调试故障,以及如何使用 HTTPS 来保护服务。

接下来的章节集中讨论了 AKS 的安全性。你了解了 Kubernetes 中的基于角色的访问控制以及如何将 AKS 与 Azure Active Directory 集成。随后,你学习了 pod 身份,并且 pod 身份在接下来的几个章节中有应用。之后,你学习了如何在 AKS 中安全存储机密信息,接着我们专注于网络安全。

本书的最后一部分集中讨论了 AKS 与其他服务的多种高级集成。你通过 Kubernetes API 部署了一个 Azure 数据库,并将其与集群中的 WordPress 应用集成。接着,你探索了如何使用 Azure Security Center 监控配置并修复集群中的威胁。然后,你在集群中部署了 Azure Functions,并通过 KEDA 进行扩展。在最后一章中,你配置了一个 CI/CD 管道,基于代码更改自动将应用部署到 Kubernetes。

如果你已经成功完成了本书中提供的所有示例,那么你现在应该已经准备好在 AKS 上构建和运行大规模应用。

posted @ 2025-06-30 19:27  绝不原创的飞龙  阅读(30)  评论(0)    收藏  举报