精通-AWS-上的弹性-Kubernetes-服务-全-

精通 AWS 上的弹性 Kubernetes 服务(全)

原文:annas-archive.org/md5/c0b687c0b1e6556e53c8d2e8ba058190

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

欢迎!这是一本关于使用弹性 Kubernetes 服务EKS)在 AWS 上轻松部署和管理 Kubernetes 集群的实用书籍。使用 EKS,运行 Kubernetes 变得轻而易举,因为你不再需要担心管理底层基础设施的复杂性。KubernetesK8s)是全球发展最快的开源项目之一,并迅速成为云原生应用程序的事实标准容器编排平台。

但是,对于那些不熟悉 AWS 的人来说,你可能会想,“为什么在 AWS 上运行 Kubernetes 这么有挑战?” 这里有一些因素可能使之变得困难。一个主要的问题是配置和管理 AWS 基础设施,包括虚拟网络和安全组。此外,管理 Kubernetes 集群所需的资源本身也可能带来一些挑战。与其他 AWS 服务(如负载均衡器和存储)集成,也可能引入复杂性。然而,EKS 已经启用了许多功能来简化这些问题,因此请放心,通过时间和努力,你可以精通在 AWS 上管理 Kubernetes 集群——而且回报将是值得的。

本书详细介绍了 AWS 托管的 EKS 服务,从其基本架构和配置到高级用例,如 GitOps 或服务网格。该书旨在带领读者从对 K8s 和 AWS 平台的基础理解,逐步掌握如何创建 EKS 集群,并在其上构建和部署生产工作负载。

在本书中,我们将深入探讨各种技术,帮助你优化 EKS 集群。内容涵盖广泛,包括网络、安全、存储、扩展、可观测性、服务网格和集群升级策略。我们将本书结构化为一个逐步引导你掌握 AWS 上 EKS 的指南。每章涵盖一个特定主题,并包括实用示例、技巧和最佳实践,帮助你理解并在现实场景中应用这些概念。

我们的目标不仅是帮助你掌握成功所需的技术技能,还希望加深你对基础概念的理解,以便你能够将其应用于自己独特的情况。

本书的读者群体

本书面向那些对 AWS 平台和 K8s 几乎没有经验的工程师和开发人员,旨在帮助他们了解如何使用 EKS 在其环境中运行容器化工作负载,并将其与其他 AWS 服务集成。这是一本实用的指南,包含大量代码示例,因此建议熟悉 Linux、Python、Terraform 和 YAML。

总的来说,这本书的目标读者主要包括三个角色,他们将从中获得实用的见解:

  • 开发人员和 DevOps 工程师:他们将理解 AWS 上的 Kubernetes 环境,知道如何通过 EKS 配置集群以运行云原生应用程序,并学习 CI/CD 实践。

  • 云架构师:他们将全面了解在 AWS 上运行 Kubernetes 时,如何设计架构良好的云基础设施。

  • Kubernetes 管理员:集群管理员将学习如何在 AWS 上管理 Kubernetes 工作负载的实际操作方法。此外,他们将全面了解 EKS 的功能,以增强集群的可扩展性、可用性和可观察性。

无论你是刚刚开始学习云计算,还是希望拓展知识和技能,本书都适合每个拥有 AWS 账户并希望开始 EKS 之旅的人。

本书涵盖的内容

第一章Kubernetes 和容器的基础知识,介绍了 Kubernetes 和容器技术。它还深入讲解了容器的构成元素、容器编排器的概念以及 Kubernetes 架构。

第二章介绍 Amazon EKS,提供了全面的指南,解释了什么是 Amazon EKS、它背后的架构、定价模型以及用户可能犯的常见错误。本章还简要概述了在 AWS 上运行工作负载的选择:使用 EKS 或自管理的 Kubernetes 集群。

第三章构建你的第一个 EKS 集群,一步步探索创建第一个 EKS 集群的不同选项,并概述了构建工作流时的自动化过程,包括 AWS 控制台、AWS CLI、eksctl、AWS CDK 和 Terraform。

第四章在 EKS 上运行你的第一个应用程序,讲解了在 EKS 上部署和操作一个简单应用程序的不同方式,包括如何实现并暴露应用程序以使其对外可访问。它还涉及了可视化工作负载的工具。

第五章使用 Helm 管理 Kubernetes 应用程序,重点介绍了如何安装和使用 Helm 简化 Kubernetes 部署体验。本章还涉及 Helm 图表的细节、架构以及常见的使用场景。

第六章在 EKS 上保护和访问集群,深入探讨了 Kubernetes 中身份验证和授权的基本方面,以及它们如何应用于 EKS。本章解释了配置客户端工具和安全访问 EKS 集群的重要性。

第七章EKS 中的网络,解释了 Kubernetes 网络,并演示了如何将 EKS 与 AWS 虚拟私有云 (VPC) 无缝集成。

第八章管理 EKS 中的工作节点,探讨了 EKS 工作节点的配置和有效管理。强调了使用 EKS 优化镜像(AMIs)和托管节点组的好处,并提供了它们相对于自管理替代方案的优势。

第九章EKS 中的高级网络配置,深入探讨了 EKS 中的高级网络场景。内容涵盖了管理 Pod IP 地址(使用 IPv6)、实施网络策略以控制流量,以及利用复杂的基于网络的信息系统(如 Multus CNI)等话题。

第十章升级 EKS 集群,重点介绍了升级 EKS 集群以利用新特性并确保持续支持的策略。提供了关于关键领域的指导,包括控制平面、关键组件、节点组的就地升级和蓝绿升级,以及将工作负载迁移到新集群的内容。

第十一章构建应用并推送到 Amazon ECR,探讨了在 Amazon ECR 上构建和存储容器镜像以供 EKS 部署使用的过程。内容涵盖了存储库认证、推送容器镜像、利用 ECR 的高级功能以及将 ECR 集成到 EKS 集群中的话题。

第十二章使用 Amazon 存储部署 Pods,解释了 Kubernetes 卷、容器存储接口CSI)以及在 Kubernetes Pods 中对持久化存储的需求,并展示了如何在 EKS 上使用 EBS 和 EFS。还介绍了安装和配置 AWS CSI 驱动程序,以便在应用中使用 EBS 和 EFS 卷的详细信息。

第十三章使用 IAM 为应用授权访问权限,讨论了 Pod 安全性,并结合了将 IAM 与容器化应用集成的场景。内容包括定义 Pod 的 IAM 权限、使用IAM 服务账户角色IRSA)以及排查与 EKS 部署相关的 IAM 问题。

第十四章为 EKS 应用设置负载均衡,探讨了 EKS 应用负载均衡的概念。还扩展了可扩展性和弹性的话题,并提供了关于 AWS 中可用的弹性负载均衡器ELB)选项的见解。

第十五章使用 AWS Fargate,介绍了 AWS Fargate 作为在 EKS 中托管 Pods 的无服务器替代方案。分析了使用 Fargate 的好处,提供了创建 Fargate 配置文件、将 Pods 无缝部署到 Fargate 环境的指导,以及排查可能出现的常见问题。

第十六章与服务网格一起工作,探讨了使用服务网格技术来增强 EKS 上微服务生态系统的控制、可见性和安全性。本章涵盖了 AWS App Mesh Controller 的安装、与 Pods 的集成、利用 AWS Cloud Map 以及故障排查 Envoy 代理。

第十七章EKS 可观察性,描述了在 EKS 部署中可观察性的重要性,并提供了关于监控、日志记录和追踪技术的见解。本章涵盖了用于监控 EKS 集群和 Pod 的原生 AWS 工具,使用 Managed Prometheus 和 Grafana 构建仪表板,利用 OpenTelemetry,以及利用机器学习功能通过 DevOps Guru 捕获集群状态。

第十八章扩展你的 EKS 集群,讨论了 EKS 中容量规划的挑战,并探讨了用于扩展集群以满足应用需求的各种策略和工具,同时优化成本。本章介绍了如何使用 Cluster Autoscaler 和 Karpenter 扩展节点组,如何使用水平 Pod 自动扩展器HPA)扩展应用程序,描述自定义指标的使用案例,以及如何利用 KEDA 优化事件驱动的自动扩展。

第十九章在 EKS 上开发,探讨了如何通过不同的自动化工具和 CI/CD 实践来提高开发人员和 DevOps 工程师在构建 EKS 集群时的效率。本章重点介绍了包括 Cloud9、EKS 蓝图、Terraform、CodePipeline、CodeBuild、ArgoCD 和 GitOps 在内的各种工具,以简化工作负载部署。

第二十章排查常见问题,提供了 EKS 故障排查检查表,并讨论了常见问题及其解决方案。

为了充分利用本书的内容

你将需要一个 AWS 账户和一个操作系统来运行下表中列出的应用程序。为了确保顺利阅读,建议具备基本的 AWS 概念知识,如虚拟私有云VPC)、弹性块存储EBS)、EC2、弹性负载均衡器ELB)、身份与访问管理IAM)以及 Kubernetes。

本书涵盖的软件 先决条件
亚马逊弹性 Kubernetes 服务(EKS) 一个 AWS 账户
亚马逊命令行界面(AWS CLI) Linux/macOS/Linux
kubectl Linux/macOS/Linux
eksctl Linux/macOS/Linux
Helm Linux/macOS/Linux
Lens (Kubernetes IDE) Linux/macOS/Linux

本书将探索各种工具,并学习如何管理 AWS 上的 Kubernetes 集群。你可以通过以下指南找到最新版本并下载所需的软件:

我们尽力将本书制作成一本实用的书籍,包含了大量的代码示例。为了充分利用这本书,你应该对 AWS、Linux、YAML 和 K8s 架构有基本的了解。

下载彩色图像

我们还提供了一个 PDF 文件,包含了本书中使用的截图和图表的彩色版本。你可以在这里下载:packt.link/g2oZN

使用的约定

本书中使用了一些文本约定。

文本中的代码:表示文本中的代码词语、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。举个例子:“MAINTAINERCMD命令不会生成层。”

一段代码块的格式如下:

apiVersion: v1 
kind: Pod 
metadata: 
  name: nginx 
spec: 
  containers:

所有的命令行输入或输出格式如下:

$ docker run hello-world

粗体:表示新术语、重要词汇,或屏幕上出现的词语。例如,菜单或对话框中的词语会以粗体显示。举个例子:“最常见的例子是OverlayFS,它包含在 Linux 内核中,并且 Docker 默认使用它。”

提示或重要说明

显示方式如下。

联系我们

我们总是欢迎读者的反馈。

一般反馈:如果你对本书的任何部分有问题,可以通过 email 联系我们,邮箱地址是 customercare@packtpub.com,并在邮件主题中提及书名。

勘误表:虽然我们已尽一切努力确保内容的准确性,但错误还是难免。如果你发现了本书中的错误,我们将感激你向我们报告。请访问www.packtpub.com/support/errata并填写表格。

盗版:如果你在互联网上发现我们作品的任何非法复制品,我们将非常感谢你提供其地址或网站名称。请通过 email 联系我们,邮箱是 copyright@packt.com,并附上相关材料的链接。

如果你有兴趣成为作者:如果你在某个领域具有专业知识,并且有兴趣写作或参与编写一本书,请访问authors.packtpub.com

分享你的想法

一旦你阅读完《掌握 AWS 上的弹性 Kubernetes 服务(EKS)》后,我们很想听听你的想法!请点击这里,直接进入本书的 Amazon 评论页面并分享你的反馈。

你的评论对我们以及技术社区都很重要,它将帮助我们确保提供优质的内容。

下载本书的免费 PDF 副本

感谢购买本书!

喜欢随时随地阅读,但又无法随身携带纸质书籍吗?

您的电子书购买是否与您选择的设备不兼容?

不用担心,现在购买每本 Packt 书籍时,您都可以免费获得该书的无 DRM PDF 版本。

在任何地方、任何设备上阅读。搜索、复制并粘贴您最喜欢的技术书籍中的代码,直接应用到您的应用程序中。

优惠不仅如此,您还可以获得独家折扣、时事通讯以及每日送到您邮箱的精彩免费内容。

按照以下简单步骤即可享受福利:

  1. 扫描二维码或访问下面的链接

下载本书的免费 PDF 副本

packt.link/free-ebook/9781803231211

  1. 提交您的购买凭证

  2. 就这样!我们会直接将您的免费 PDF 及其他福利发送到您的电子邮件。

第一部分:开始使用 Amazon EKS

在本部分中,您将全面了解 Kubernetes 和容器,并深入了解 Amazon EKS 及其架构。您还将通过逐步指南,准备好您的 EKS 集群。在本部分结束时,您将学习如何在 EKS 上部署和操作应用程序的基础知识,并了解如何使用 Helm 简化 Kubernetes 应用程序的部署。

本部分包含以下章节:

  • 第一章Kubernetes 和容器基础

  • 第二章介绍 Amazon EKS

  • 第三章构建您的第一个 EKS 集群

  • 第四章在 EKS 上运行您的第一个应用程序

  • 第五章使用 Helm 管理 Kubernetes 应用程序

第一章:Kubernetes 和容器的基础知识

随着越来越多的组织采用敏捷开发和现代(云原生)应用架构,需要一个能够部署、扩展和提供可靠容器服务的平台已成为许多中型和大型公司的关键。Kubernetes 已成为托管容器工作负载的事实标准平台,但安装、配置和管理可能会很复杂。

弹性 Kubernetes 服务EKS)是 AWS 平台上的托管服务,使用户能够专注于使用 Kubernetes 集群,而不是花费时间进行安装和维护。

在本章中,我们将回顾 Kubernetes 的基本构建块。具体而言,我们将涵盖以下主题:

  • Docker、containerd 和 runc 的简要历史

  • 对容器的深入探讨

  • 什么是容器编排?

  • Kubernetes 是什么?

  • 理解 Kubernetes 部署架构

为了更深入地理解本章内容,建议您对 Linux 命令和架构有一定了解。

重要提示

本书内容适用于具有在本地或其他云平台上构建和/或运行 Kubernetes 经验的 IT 专业人员。我们意识到,并非每个具备先决条件经验的人都了解 Kubernetes 的背景,因此本书的第一章(可选)旨在提供一个一致的视角,阐明 Kubernetes 的起源及其支持技术。如果您认为您已经对本章讨论的主题有清晰的理解,请随意跳过此章节,转而阅读下一章节。

Docker、containerd 和 runc 的简要历史

IT 行业经历了多次变革:从上世纪 70-80 年代的大型专用主机和 UNIX 系统,到上世纪初 Solaris Zones、VMware 的虚拟化运动,以及 Linux 内核中 cgroupsnamespaces 的开发。2008 年,发布了 LXC。它提供了一种在 Linux 内核中本地管理 cgroups 和 namespaces 的一致方式,以允许虚拟化。主机系统对 容器 没有概念,因此 LXC 编排底层技术来创建一组隔离的进程,即容器。

Docker 于 2013 年发布,最初是建立在 LXC 之上,并围绕容器管理引入了整个生态系统,包括一个打包格式(Dockerfile),该格式利用联合文件系统使开发者能够构建轻量级的容器镜像,以及一个管理 Docker 容器、容器存储和 CPU、RAM 限制等的运行时环境,同时管理和传输镜像(Docker 守护进程),并提供一个可以被 Docker CLI 消耗的应用程序接口API)。Docker 还提供了一套注册中心(Docker Hub),允许操作系统、中间件和应用程序供应商在容器中构建和分发他们的代码。

2016 年,Docker 将这些运行时功能提取到一个独立的引擎 containerd 中,并将其捐赠给了 Cloud Native Compute FoundationCNCF),使得其他容器生态系统如 Kubernetes 可以部署和管理容器。Kubernetes 最初使用 Docker 作为其容器运行时,但在 Kubernetes 1.15 中引入了容器运行时接口CRI),这使得 Kubernetes 可以使用不同的运行时,例如 containerd。

开放容器倡议OCI)由 Docker 和容器行业成立,旨在提供一个低级接口来管理容器。他们开发的第一个标准之一是 OCI 运行时规范,该规范采用 Docker 镜像格式作为所有镜像规范的基础。runc 工具由 OCI 开发,用于实现其运行时规范,并被大多数运行时引擎(如 containerd)采纳,作为管理容器和镜像的低级接口。

以下图示展示了我们在本节中讨论的所有概念如何结合在一起:

图 1.1 – 容器运行时

图 1.1 – 容器运行时

在本节中,我们讨论了容器的历史以及用于创建和管理容器的各种技术。在下一节中,我们将深入探讨容器的实际构成。

深入了解容器

容器是一个纯粹的逻辑构造,由一组技术组成,通过容器运行时将它们“粘合”在一起。本节将提供关于在 Linux 内核中用于创建和管理容器的技术的更详细视图。两个基础性的 Linux 服务是命名空间和控制组:

  • 命名空间(在 Linux 上的上下文):命名空间是 Linux 内核的一个特性,用于将内核资源进行分区,使得在命名空间内运行的进程可以与其他进程隔离。每个命名空间将拥有自己的进程 IDPIDs)、主机名、网络访问权限等。

  • 控制组:控制组(cgroup)用于限制进程或进程集对 CPU、RAM、磁盘 I/O 或网络 I/O 等资源的使用。最初由 Google 开发,这项技术已被纳入 Linux 内核。

Linux 中命名空间和控制组的结合使得容器可以定义为一组隔离的进程(命名空间)和有资源限制(控制组)的进程:

图 1.2 – 作为 cgroup 和命名空间组合的容器

图 1.2 – 作为 cgroup 和命名空间组合的容器

容器运行时镜像的创建方式非常重要,因为它直接影响容器的工作方式和安全性。联合文件系统UFS)是一种用于容器镜像的特殊文件系统,接下来将讨论它。

了解联合文件系统

UFS 是一种文件系统,可以将多个目录/文件合并/覆盖为一个统一的视图。它还呈现出一个可写的单一文件系统的外观,但实际上是只读的,不允许修改原始内容。最常见的例子是 OverlayFS,它包含在 Linux 内核中,并且 Docker 默认使用它。

UFS 是一种非常高效的方式来合并容器镜像的内容。每组独立的内容被视为一个层,层可以在容器镜像之间复用。例如,Docker 会使用 Dockerfile 根据基础镜像创建一个分层文件。以下示例图显示了这一过程:

图 1.3 – 示例 Docker 镜像

图 1.3 – 示例 Docker 镜像

图 1.3 中,FROM 命令从 ubuntu 18.04 镜像创建一个初始层。两个 RUN 命令的输出创建了独立的层,而最后一步是 Docker 添加一个薄的读写层,所有对运行中容器的修改都写入该层。MAINTAINERCMD 命令不会生成层。

Docker 是最流行的容器运行时环境,支持在 Windows、macOS 和 Linux 上使用,因此它提供了一种简单的方式来学习如何构建和运行容器(但请注意,Windows 和 Linux 操作系统本质上是不同的,因此目前无法在 Linux 上运行 Windows 容器)。尽管 Docker 二进制文件已经从当前版本的 Kubernetes 中移除,但接下来的概念和技术将帮助你理解容器在基础层面的工作原理。

如何使用 Docker

开始使用容器的最简单方法是在开发机上使用 Docker。由于 OCI 已经为 Docker 镜像制定了标准化,本地创建的镜像可以在任何地方使用。如果你已经安装了 Docker,以下命令将运行一个简单的容器,使用官方的 hello-world 示例镜像并显示其输出:

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
...
Status: Downloaded newer image for hello-world:latest
Hello from Docker!

上述消息显示你的安装似乎工作正常。你可以看到 hello-world 镜像已“拉取”自仓库。默认情况下,它会从公共 Docker Hub 仓库中获取该镜像,网址为 hub.docker.com/。我们将在 第十一章中讨论仓库,特别是 AWS Elastic Container RegistryECR),内容包括 构建应用并推送到 Amazon ECR

重要提示

如果你想了解如何安装和使用 Docker,可以参考 Docker 官方文档中的 入门 指南:docs.docker.com/get-started/

同时,你可以使用以下命令列出主机上的容器:

$ docker ps -a
CONTAINER ID   IMAGE  COMMAND      CREATED       STATUS  PORTS     NAMES
39bad0810900   hello-world
"/hello"                  10 minutes ago   Exited (0) 10 minutes ago             distracted_tereshkova
...

尽管前面的命令很简单,但它们演示了构建和运行容器的便捷性。当你使用 Docker CLI(客户端)时,它会与运行时引擎——Docker 守护进程进行交互。当守护进程接收到来自 CLI 的请求时,Docker 守护进程会执行相应的操作。在 docker run 示例中,这意味着从 hello-world 镜像创建一个容器。如果镜像已存储在你的机器上,它会使用该镜像;否则,它会尝试从公共 Docker 仓库如 Docker Hub拉取该镜像。

如前一节所述,Docker 现在使用 containerd 和 runc。你可以使用 docker info 命令查看这些组件的版本:

$ docker info
…
  buildx: Docker Buildx (Docker Inc., v0.8.1)
  compose: Docker Compose (Docker Inc., v2.3.3)
  scan: Docker Scan (Docker Inc., v0.17.0)
……
containerd version: 2a1d4dbdb2a1030dc5b01e96fb110a9d9f150ecc
 runc version: v1.0.3-0-gf46b6ba
 init version: de40ad0
...

在本节中,我们回顾了 Linux 中支持容器的底层技术。在接下来的章节中,我们将更详细地探讨容器编排和 Kubernetes。

什么是容器编排?

Docker 在单一机器上运行良好,但如果你需要在多个不同的机器上部署成千上万个容器呢?这正是容器编排所要做的:调度、部署和管理跨环境的成百上千个容器。现在有多个平台尝试做到这一点:

  • Docker Swarm:Docker 提供的集群管理和编排解决方案 (docs.docker.com/engine/swarm/)。

  • KubernetesK8s):一个开源的容器编排系统,最初由 Google 设计,现在由 CNCF 维护。得益于开源社区的积极贡献,Kubernetes 拥有强大的生态系统,涵盖了部署、调度、扩展、监控等一系列解决方案(https://kubernetes.io/)。

  • Amazon Elastic Container ServiceECS):AWS 提供的高度安全、可靠且可扩展的容器编排解决方案。ECS 的概念与许多其他编排系统类似,它也能轻松地运行、停止和管理容器,并与其他 AWS 服务如 CloudFormation、IAM 和 ELB 等集成(更多信息请参见 https://ecs.aws/)。

控制/数据平面,这是容器编排器常见的架构,见下图:

图 1.4 – 容器编排概述

图 1.4 – 容器编排概述

容器编排通常由决定容器放置位置的 大脑 或调度器/编排器(控制平面)组成,而 工作节点 则运行实际的容器(数据平面)。编排器提供了一些附加功能:

  • 维护整个集群系统的期望状态

  • 配置并调度容器

  • 当工作节点不可用时,重新调度容器

  • 故障恢复

  • 根据工作负载指标、时间或某些外部事件,横向或纵向扩展容器

我们已经从概念层面讨论了容器编排,现在让我们看看 Kubernetes,使这个概念变得更加 真实

什么是 Kubernetes?

Kubernetes 是一个开源容器编排工具,最初由 Google 开发,但现在被许多组织视为事实上的容器平台。Kubernetes 被部署为包含一个控制平面,该控制平面提供一个 API,暴露 Kubernetes 操作;一个调度器,它负责将容器(下文将讨论 Pods)调度到工作节点上;一个数据存储,用于存储所有集群数据和状态(etcd);以及一个控制器,它管理作业、故障和重启。

图 1.5 – Kubernetes 概述

图 1.5 – Kubernetes 概述

集群还由许多工作节点组成,这些节点构成数据平面。每个节点运行 kubelet 代理,确保容器在特定节点上运行,并且运行 kube-proxy,管理节点的网络。

Kubernetes 的主要优势之一是,所有资源都被定义为可以创建、读取、更新和删除的对象。下一节将回顾主要的 K8s 对象,或者说它们通常被称为“种类”,这些是你通常会操作的对象。

Kubernetes 关键 API 资源

容器化的应用程序将通过 API 在工作节点上部署并启动。API 提供了一个抽象对象,称为 Pod,它定义为一个或多个共享相同 Linux 命名空间、cgroups、网络和存储资源的容器。让我们来看一个简单的 Pod 示例:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80

在这个示例中,kind 定义了 API 对象,一个单独的 Pod,而 metadata 包含了 Pod 的名称,这里是 nginxspec 部分包含一个容器,它将使用 nginx 1.14.2 镜像并暴露一个端口(80)。

在大多数情况下,您希望将多个 Pod 部署到多个节点上,并在节点故障时保持这些 Pod 的数量。为此,您可以使用 Deployment,它会保持您的 Pod 运行。Deployment 是一个 Kubernetes kind,允许您定义所需的副本数或 Pod 数量,并且结合之前看到的 Pod 规格。让我们看一个基于我们之前讨论的 nginx Pod 的示例:

ApiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

最后,您希望将您的 Pod 公开到集群外部!这是因为,默认情况下,Pod 和 Deployment 仅能从集群内的其他 Pod 访问。有多种服务,但在这里我们讨论的是 NodePort 服务,它在集群中的所有节点上公开一个动态端口。

为此,您将使用 Servicekind,一个示例如下所示:

kind: Service
apiVersion: v1
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  port: 80
  nodePort: 30163

在前面的示例中,Service 在集群中的任何主机上公开端口 30163,并将其映射回任何具有 label app=nginx(在 Deployment 中设置)的 Pod,即使某个主机上没有运行该 Pod。它将 port 值转换为端口 80,这是 nginx Pod 正在监听的端口。

在本节中,我们已经了解了基本的 Kubernetes 架构和一些基本的 API 对象。在最后一节中,我们将回顾一些标准的部署架构。

理解 Kubernetes 部署架构

部署 Kubernetes 有多种方式,取决于您是在笔记本/工作站上进行开发、部署到非生产环境或生产环境,还是您自己构建它或使用像 EKS 这样的托管服务。

接下来的几节将讨论如何为不同的开发环境部署 Kubernetes,例如在本地笔记本上进行测试或为生产工作负载进行部署。

开发者部署

对于本地开发,您可能希望使用一个简单的部署方式,比如 minikube 或 Kind。这些工具在虚拟机(minikube)或 Docker 容器(Kind)上部署完整的控制平面,并允许您在本地机器上部署 API 资源,本地机器既充当控制平面也充当数据平面。这种方法的优点是所有内容都在您的开发机器上运行,您可以轻松构建和测试应用程序,并且可以使用 Deployment 清单。然而,您只有一个工作节点,这意味着无法实现复杂的多节点应用场景。

非生产部署

在大多数情况下,非生产部署具有不可恢复的控制平面。这通常意味着只有一个主节点托管控制平面组件(API 服务器、etcd 等),而多个工作节点。这样有助于测试多节点应用程序架构,但不会增加复杂控制平面的开销。

唯一的例外是集成和/或运营性的非生产环境,在这种环境下,您可能希望在控制平面故障的情况下测试集群或应用程序操作。在这种情况下,您可能希望至少拥有两个主节点。

自建生产环境

在生产环境中,你需要一个具备韧性的控制平面,通常遵循 三节点法则,即部署 3、6 或 9 个控制节点,以确保在发生故障事件时使用奇数个节点来获得多数决策权。控制平面组件主要是无状态的,而配置则存储在 etcd 中。可以在 API 控制器之间部署负载均衡器,以提供 K8s API 请求的韧性;然而,一个关键的设计决策是如何提供一个具备韧性的 etcd 层。

在第一种模型中,堆叠式 etcd,etcd 被直接部署在主节点上,使得 etcd 和 Kubernetes 拓扑紧密耦合(参见 https://d33wubrfki0l68.cloudfront.net/d1411cded83856552f37911eb4522d9887ca4e83/b94b2/images/kubeadm/kubeadm-ha-topology-stacked-etcd.svg)。

这意味着,如果一个节点出现故障,API 层和数据持久性(etcd)层都会受到影响。解决这一问题的方法是使用一个外部 etcd 集群,将其托管在与其他 Kubernetes 组件分开的机器上,从而有效地解耦它们(参见 https://d33wubrfki0l68.cloudfront.net/ad49fffce42d5a35ae0d0cc1186b97209d86b99c/5a6ae/images/kubeadm/kubeadm-ha-topology-external-etcd.svg)。

在外部 etcd 模型中,API 或 etcd 集群中的任何一个故障都不会影响另一个集群。然而,这意味着你将需要管理和维护两倍数量的机器(无论是虚拟机还是物理机)。

托管服务环境

AWS EKS 是一项托管服务,AWS 提供控制平面,你可以使用自我管理或 AWS 管理的节点组将工作节点连接到它(参见 第八章在 EKS 上管理工作节点)。你只需创建一个集群,AWS 会配置并管理至少两个 API 服务器(位于两个不同的可用区)以及一个分布在三个可用区的独立 etcd 自动扩展组。

该集群支持 99.95% 的服务级别可用性,AWS 会修复控制平面中的任何问题。这种模式意味着你无法灵活地调整控制平面架构,但同时,你也不需要自己管理它。EKS 可以用于测试、非生产环境和生产工作负载,但请记住,每个集群都有一定的费用(这将在 第二章介绍 Amazon EKS 中讨论)。

现在你已经了解了从开发到生产中构建 Kubernetes 集群时可以实现的几种架构。在本书中,你不必自己知道如何构建整个 Kubernetes 集群,因为我们将使用 EKS。

总结

本章中,我们探讨了容器和 Kubernetes 的基本概念。我们讨论了 Docker、containerd 和 runc 在 Linux 系统上使用的核心技术概念,以及如何使用容器编排系统(如 Kubernetes)来扩展部署。

我们还介绍了什么是 Kubernetes,回顾了几个组件和 API 资源,并讨论了开发和生产环境的不同部署架构。

在下一章,我们将更详细地讨论托管的 Kubernetes 服务——Amazon Elastic Kubernetes ServiceAmazon EKS),并了解其关键优势。

进一步阅读

  • 理解 EKS SLA

https://aws.amazon.com/eks/sla/

  • 理解 Kubernetes API

https://kubernetes.io/docs/concepts/overview/kubernetes-api/

  • 开始使用 minikube

https://minikube.sigs.k8s.io/docs/start/

  • 开始使用 Kind

https://kind.sigs.k8s.io/docs/user/quick-start/

  • EKS 控制平面 最佳实践

https://aws.github.io/aws-eks-best-practices/reliability/docs/controlplane/

  • 开放容器 倡议文档

https://opencontainers.org/

第二章:介绍 Amazon EKS

在上一章中,我们讨论了容器、容器编排和 Kubernetes 的基本概念。自己构建和管理 Kubernetes 集群可能是一项非常复杂且耗时的任务,但使用托管 Kubernetes 服务可以去除这些繁重的工作,使用户能够专注于应用程序的开发和部署。

本章将高层次地探讨 弹性 Kubernetes 服务 (EKS) 及其技术架构,以便更好地理解其优点和缺点。

总结来说,本章涵盖以下主题:

  • 什么是 Amazon EKS?

  • 理解 EKS 架构

  • 调查 Amazon EKS 定价模型

  • 使用 EKS 时常见的错误

技术要求

你应该对以下内容有所了解:

  • 什么是 Kubernetes 及其工作原理(参考 第一章Kubernetes 和容器的基础知识

  • AWS 基础服务,包括 虚拟私有云 (VPC)、弹性计算云 (EC2)、弹性块存储 (EBS)、和 弹性负载均衡器 (ELB)

  • 对标准 Kubernetes 部署工具的基本了解

什么是 Amazon EKS?

根据 云原生计算基金会 (CNCF) 的数据,到 2017 年底,近 57% 的 Kubernetes 环境运行在 AWS 上。最初,如果你想在 AWS 上运行 Kubernetes,必须使用 Rancher 或 Kops 等工具,在 EC2 实例上构建集群。你还需要持续监控和管理集群,部署开源工具如 Prometheus 或 Grafana,并且有一个运营团队确保集群的可用性,并管理升级过程。Kubernetes 还有定期发布的周期:截至 2021 年 6 月,每年发布三次!这也导致了升级集群的持续运营压力。

由于 AWS 服务路线图主要由客户需求推动,构建和运行 Kubernetes 在 AWS 上的工作量促使 AWS 服务团队在 2018 年 6 月发布了 EKS。

Amazon EKS 就是 Kubernetes!AWS 将开源代码进行修改,添加了特定于 AWS 的插件,用于身份验证和网络管理(本书后面会讨论),并允许你在 AWS 账户中部署 Kubernetes。AWS 随后管理控制平面,并允许你将计算和存储资源连接到其中,运行 Pods 并存储 Pod 数据。

如今,Amazon EKS 已被全球许多领先企业采用——如 Snap Inc.、汇丰银行(HSBC)、外卖英雄(Delivery Hero)、富达投资(Fidelity Investments)等。它简化了在 AWS 上构建、保护和遵循最佳实践的 Kubernetes 管理过程,为组织带来好处,使他们能够专注于构建基于容器的应用,而不是从零开始创建 Kubernetes 集群。

云原生计算基金会

CNCF 是一个成立于 2015 年的 Linux 基金会项目,负责推动 Kubernetes 开发以及其他云原生项目的发展。CNCF 目前拥有 600 多名成员,包括 AWS、Google、Microsoft、Red Hat、SAP、华为、Intel、Cisco、IBM、Apple 和 VMware。

为什么选择 Amazon EKS?

使用 EKS 的主要优点是您无需再管理控制平面;即使是升级,也只需要单击一下操作。虽然听起来很简单,但让 AWS 负责部署、扩展、修复和升级控制平面在生产环境中或当您有多个集群时所带来的运营节省是无法低估的。

由于 EKS 是一个托管服务,它也与 AWS 生态系统深度集成。这意味着:

  • Pods 是一级网络实体,具有 VPC 网络地址,并且可以像其他 AWS 资源一样进行管理和控制。

  • 可以为 Pods 分配特定的身份与访问管理IAM)角色,从而简化基于 Kubernetes 的应用程序如何连接和使用 AWS 服务,如 DynamoDB。

  • Kubernetes 的控制平面和数据平面日志及度量可以发送到 AWS CloudWatch,在那里可以进行报告、管理和可视化,而无需额外的服务器或软件。

  • 运营和开发团队可以混合使用计算(EC2 和/或 Fargate)和存储服务(EBS 和/或 EFS)来支持各种性能、成本和安全需求。

重要说明

重要的是要理解,EKS 主要是一个托管控制平面。数据平面使用标准的 AWS 服务(如 EC2 和 Fargate)提供 Pods 的运行环境。在大多数情况下,数据平面由运营或开发团队管理。

在后续章节中,我们将深入探讨这些领域,并说明如何使用和配置它们。但现在,让我们继续讨论自管 K8s 集群和 EKS 之间的区别。

自管 Kubernetes 集群与 Amazon EKS

以下表格比较了自建集群与 EKS 的两种方式:

自管 Kubernetes 集群 EKS
完全控制 大部分(无法直接访问底层控制平面服务器)
Kubernetes 版本 社区发布 社区发布
版本支持 Kubernetes 项目维护最近三个小版本的发布分支。从 Kubernetes 1.19 开始,版本将获得大约 1 年的补丁支持。Kubernetes 1.18 及更早版本的补丁支持大约为 9 个月。 Kubernetes 版本在首次在 Amazon EKS 上可用后支持 14 个月,即使该版本不再由 Kubernetes 项目/社区支持。
网络访问控制 手动设置和配置 VPC 控制 EKS 创建标准安全组并支持公共 IP 白名单。
身份验证 手动设置和配置 Kubernetes RBAC 控制 与 AWS IAM 集成
可扩展性 手动设置和配置扩展 管理的控制平面和标准计算/存储扩展
安全性 手动打补丁 控制平面的补丁由 AWS 进行
升级 手动更新和替换组件 单击一下即可升级控制平面,同时管理的节点组支持更简便的升级
监控 需要自行监控并支持监控平台 EKS 会进行监控并替换不健康的主节点,集成了 CloudWatch

表 2.1 – 自管理 Kubernetes 和 EKS 的比较

在下一部分,我们将深入探讨 EKS 架构,让你开始真正理解自管理集群和 EKS 之间的差异。

理解 EKS 架构

每个 EKS 集群都有一个用于工具(如 kubectl,主要的 Kubernetes 客户端)的单一端点 URL。此 URL 隐藏了在 AWS 管理的 VPC 中跨多个可用区部署的所有控制平面服务器,且构成控制平面的服务器对集群的用户或管理员不可访问。

数据平面通常由部署在多个可用区的 EC2 工作节点组成,并且配置了kubeletkube-proxy代理,这些代理指向集群端点。下图展示了标准的 EKS 架构:

图 2.1 – EKS 架构的高级概览

图 2.1 – EKS 架构的高级概览

下一部分将探讨 AWS 如何配置和保护 EKS 控制平面,并介绍你可以用来与之交互的具体命令。

理解 EKS 控制平面

当创建新集群时,会在 AWS 所有的 VPC 中创建一个新的控制平面,且该 VPC 位于一个独立账户中。每个控制平面至少有两个 API 服务器,分布在两个可用区以增强弹性,并通过公共网络负载均衡器NLB)暴露。etcd 服务器分布在三个可用区,并配置为自动扩展组,同样是为了增强弹性。

集群的管理员和/或用户无法直接访问集群的服务器;他们只能通过负载均衡器访问 K8s API。API 服务器通过在两个可用区中创建弹性网络接口ENIs)与在客户拥有的不同账户/VPC 下运行的工作节点集成。运行在工作节点上的 kubelet 代理使用附加到工作节点 VPC 的 Route 53 私有托管区来解析与 ENIs 相关联的 IP 地址。下图展示了这一架构:

图 2.2 – 详细的 EKS 架构

图 2.2 – 详细的 EKS 架构

重要提示

这个架构的一个关键 陷阱 是,目前没有私有的 EKS 端点,因此工作节点需要互联网访问权限才能通过 AWS EKS DescribeCluster API 获取集群详情。这通常意味着带有工作节点的子网需要有互联网/NAT 网关或通向互联网的路由。

理解集群安全性

当创建新集群时,系统会同时创建一个新的安全组,并控制对 API 服务器 ENI 的访问。集群安全组必须配置为允许需要访问 API 服务器的任何网络地址。如果是公共集群(在 第七章 中讨论的 EKS 网络),这些 ENI 仅由工作节点使用。当集群是私有的时,这些 ENI 也用于客户端(kubectl)访问 API 服务器;否则,所有 API 连接都通过公共端点进行。

通常,为工作节点配置独立的安全组,并允许访问与数据平面相关的节点之间的通信。AWS 提供了一项名为 安全组引用 的功能,允许你从另一个安全组中引用现有的安全组。这简化了通过引用集群安全组中的任何工作节点安全组来将工作节点连接到集群 ENI 的过程。你需要从工作节点安全组中允许的最小访问权限是 HTTPS(TCP 443)、DNS(TCP/UDP 53)以及 kubelet 命令和日志(TCP 10250)。以下图示说明了这种架构。

图 2.3 – EKS 安全组

图 2.3 – EKS 安全组

通过命令行了解你的集群

让我们使用 AWS 和 kubectl 的 aws eks list-clusters 命令:

$ aws eks list-clusters
{
    "clusters": [
        "mycluster13DCA0395 "
    ]}

在前面的输出中,我们可以看到列出了一个集群。我们可以使用 aws eks describe-cluster --name 命令获取更多细节:

$ aws eks describe-cluster --name mycluster13DCA0395
{
    "cluster": {
        "status": "ACTIVE",
        "endpoint": "https://12.gr7.eu-central-1.eks.amazonaws.com",
………..
        "name": "mycluster13DCA0395 ",
……
            "endpointPublicAccess": true,
            "endpointPrivateAccess": true
…………}

前面的输出已被截断,但显示了位于 eu-central-1 区域的端点。我们可以看到集群的名称,并且端点已设置为允许 PublicAccess(互联网访问)和 PrivateAccess(VPC 内部访问)。这意味着你的客户端(例如 kubectl)可以通过互联网或从任何可以路由到 VPC 的连接中访问集群(前提是访问列表、防火墙规则和安全组允许访问)。

在我们可以使用 kubectl 之前,需要做一个额外的步骤,即使用 aws eks update-kubeconfig 命令设置配置文件中的相关证书和上下文,以允许 kubectl 与集群通信。这个过程可以手动完成,但使用 AWS CLI 命令会更简单:

$ aws eks update-kubeconfig --name mycluster13DCA03950b0
Updated context arn:aws:eks:eu-central-1:676687:cluster/mycluster13DCA0395 in /../.kube/config

重要注意事项

你需要拥有 AWS EKS 的 IAM 权限来执行这些命令,同时需要 K8s RBAC 权限来执行 kubectl 命令,即使你已经拥有对集群端点的网络访问权限。

如果您使用 kubectl cluster-infokubectl version 命令,您将看到与 aws eks describe-cluster 命令类似的信息。集群节点、存储和 Pod 的详细信息可以通过 kubectl 命令 get nodesget pvget po 来确定,如下所示。命名空间和排序命令修饰符可用于帮助输出:

$ kubectl get nodes
NAME           STATUS   ROLES    AGE   VERSION
ip-x.x.x.x.    Ready    <none>   25d   v1.21.5-eks-9017834
ip-x.x.x.x.    Ready    <none>   25d   v1.21.5-eks-9017834
$kubectl get pv --sort-by=.spec.capacity.storage
No resources found
$ kubectl get po --all-namespaces
NAMESPACE     NAME    READY   STATUS    RESTARTS   AGE
kube-system   aws-node-d2vpk    1/1     Running   0  1d
kube-system   aws-node-ljdz6  1/1     Running   0 1d
kube-system   coredns-12   1/1     Running   0 1d
kube-system   coredns-12   1/1     Running   0   1d
kube-system   kube-proxy-bhw6p 1/1 Running   0   1d
kube-system   kube-proxy-fdqlb  1/1 Running   0          1d

之前的输出告诉我们,集群有两个工作节点,没有配置物理卷,并且仅托管 coredns(集群 DNS 服务)、kube-proxy(集群网络)和 aws-node(AWS VPC CNI)等关键集群服务。

现在我们已经了解了什么是 EKS,让我们看看它在 AWS 中的定价。

调查 Amazon EKS 定价模型

在本节中,我们将简要概述 Amazon EKS 定价模型。由于 AWS 的定价模型会时常变动,建议您查看最新的 Amazon EKS 定价页面,以获取更多详细信息:

单个集群将产生两种类型的费用:

  • EKS 控制平面的固定月度费用

  • 来自计算、网络和存储资源的可变成本

固定的控制平面费用

控制平面定价固定为每小时 0.10 美元,相当于每个集群每月 73 美元(如以下计算所示)。这与控制平面由 AWS 管理的任何扩展或故障恢复活动无关。

1 集群 x 每小时 0.10 美元 x 每月 730 小时 = 73.00 美元

可变成本

控制平面本身无法真正工作。它需要计算资源,以便可以实际调度和托管 Pods。反过来,计算平台需要存储和网络才能正常运行,但这些资源是基于可变成本模型的,成本会根据使用量波动。

EKS 工作节点有两种计算选项,稍后我们会详细讨论(EC2 和 Fargate):

  • 使用 EC2 时,费用将根据以下因素波动:

    • EC2 实例的大小和数量

    • 附加到实例的存储量

    • 它们部署的区域

    • 定价模型的类型:按需、预留实例、抢占实例或您拥有的任何节省计划

  • 使用 Fargate 时,费用将根据以下因素波动:

    • Fargate 实例的数量

    • 实例操作系统/CPU 处理器

    • 它们部署的区域

    • 每个实例/每小时的 CPU/RAM 费用

    • 每个实例的存储量(以 GB 为单位)

EC2 工作节点将彼此通信,控制平面和 Pods 也将进行通信,跨工作节点互相传输数据,在某些情况下,还会涉及到 VPC 外部的通信。AWS 会对外发流量、跨可用区流量以及像 Transit 或 NAT 网关这样的网络服务收费。估算网络流量成本是最具挑战性的活动之一,因为在大多数情况下,你几乎不了解流量将在应用中如何路由,也不了解数据包大小、每秒数据包数等技术细节。网络流量估算超出了本书的范围,但有一些最佳实践是你应该尝试并遵守的:

  • 设计和部署应用程序时,尽量让尽可能多的流量留在工作节点内部。例如,如果两个服务需要相互通信,可以使用 Pod 亲和性 标签(仅限 EC2)确保它们共存于同一个节点/节点上。

  • 设计你的 AWS VPC,确保流量留在相同的可用区内;例如,如果你的工作节点分布在两个可用区中以增强可靠性,可以部署两个 NAT 网关(每个可用区一个)以减少跨可用区的网络费用。当然,如果你的互联网流量非常少,这可能会变得更昂贵,因此仍然需要充分了解你的应用网络配置。

  • 与 AWS 服务(如 DynamoDB 等)进行通信时,使用私有端点来减少网络外发费用。

我们已经了解了理论,接下来的部分将通过一些具体的示例使其更加清晰。

估算 EKS 集群的费用

为了更好地帮助你理解如何估算运行 EKS 集群时的费用,以下是一些在 AWS 上测量费用的示例。

示例 1 – 通过启动 EC2 实例运行带有工作节点的 EKS 集群

假设你选择在 AWS 美国东部区域(北弗吉尼亚,us-east-1)创建 EKS 集群,并使用以下设置:

  • 3 个按需 Linux EC2 实例,使用 m5.large 作为工作节点,每个实例附带 20 GB 的 EBS 存储 gp2 卷(通用型 SSD)

  • 一个整月都可用的集群(30 天,730 小时)

每月费用可以通过以下公式估算:

1 集群 x 0.10 USD 每小时 x 730 小时每月 = 73.00 USD

3 实例 x 0.096 USD x 730 小时每月 = 210.24 USD (每月 按需费用)

30 GB x 0.10 USD x 3 实例 = 9.00 USD(**EBS 费用)

所有这些费用加总后,每月费用为 292.24 美元。

重要提示

这只是一个简单示例;实际上,这些费用可以(并且应该)通过使用节省计划或预留实例,以及像 gp3 这样的存储选项大幅减少。

示例 2 – 使用 AWS Fargate 运行 EKS 集群

在前面的例子中,每个 m5.large 实例支持 8GB 的 RAM 和 2 个 vCPU,因此我们可以将许多 Pods 部署到每个工作节点。如果我们现在选择使用 Fargate 作为计算层,我们需要考虑支持多少 Pods,因为一个 Fargate 实例只能支持一个 Pod。

假设我们有相同的集群控制平面/区域,但有以下情况:

  • 15 个 Pods 运行,并由 15 个 Fargate 实例支持,每个实例配有 1 个 vCPU 和 2 GB 内存,以及 20GB 的临时存储

  • 每天 15 个任务或 Pods(每月 730 小时 / 每天 24 小时)= 每月 456.25 个任务

月度费用可以通过以下公式估算:

1 个集群 x 每小时 0.10 美元 x 每月 730 小时 = 73.00 美元

456.25 个任务 x 1 vCPU x 1 小时 x 0.04048 美元每小时 = 18.47 美元用于 vCPU 小时

456.25 个任务 x 2.00 GB x 1 小时 x 0.004445 美元每 GB 每小时 = 4.06 美元用于 GB 小时

20 GB - 20 GB(无需额外收费) = 0.00 GB 可计费的临时存储 每个任务

所有这些成本总和每月为 95.53 美元。

如你所见,EC2 和 Fargate 之间的成本差异显著(292.24 美元 vs 95.53 美元)。然而,在 Fargate 的例子中,有 15 个 Pods/实例,并且这些 Pods 每天只执行 1 小时。如果你的应用程序不是这样运行的,那么成本会发生变化,可能会更高。另一方面,在 EC2 上,你是为计算节点付费,因此 Pods 的数量以及它们执行的时间并不重要。实际上,你可能会看到一个混合的计算环境,EC2 为长时间运行的 Pods 提供计算资源,而 Fargate 用于更多的批处理类型操作或需要增强安全性的场景。

使用 EKS 时常见的错误

最后,让我们通过讨论如何高效地配置和管理 EKS 来结束这一章,并在可能的情况下应用最佳实践。以下是一些我们常见的错误,尤其是当人们刚开始使用 EKS 时:

  • 集群一直运行:如果你不需要你的 EKS 集群,关闭它,或者至少删除或缩减节点组。为开发或测试环境创建集群(甚至仅仅是为了尝试书中的代码)会花费你真实的金钱,因此如果不使用它,关掉它。

  • aws-auth ConfigMap。请阅读 第六章EKS 上的集群安全与访问,了解更多信息。

  • Pod IP 地址耗尽:使用 AWS CNI,每个 Pod 都会分配一个 VPC IP 地址。如果你没有正确配置你的 VPC 和 EKS 集群,你将耗尽 IP 地址,集群将无法调度更多的 Pods。请阅读 第七章EKS 中的网络,了解更多信息。

  • 我的集群 IP 地址无法从我的工作站访问:集群可以是私有的(仅能从 AWS 和连接的私有网络访问)或公共的(可以从互联网访问),因此根据集群的配置,以及客户机与 API 服务器之间的防火墙设置,您可能无法访问 API 服务器。

概要

在本章中,我们描述了 EKS 只是 Kubernetes 的托管版本,主要的区别在于 AWS 会为您管理和扩展控制平面(API 服务器、etcd 等),而集群的用户/管理员负责部署计算和存储资源以托管 Pods。

我们还查看了 AWS 管理控制平面的技术架构,以及如何与其进行交互。然而,我们指出,由于它是 AWS 管理的服务,您几乎没有能力修改构成控制平面的服务器。

接着,我们查看了几种 EKS 成本模型,帮助您理解虽然控制平面成本大多是固定的,但计算和存储的成本将根据您拥有的 Pods 或 EC2 工作节点数量而变化。最后,我们讨论了首次使用 EKS 的用户常见的一些错误。

在下一章中,我们将学习如何创建 EKS 集群并设置环境。我们还将介绍如何使用不同的工具创建自己的 Amazon EKS 集群。

进一步阅读

第三章:构建您的第一个 EKS 集群

在前几章中,我们详细讨论了 Kubernetes 和 EKS。本章将开始探索如何配置和构建一个基本集群。

虽然 EKS 是 AWS 提供的托管服务,但您可以使用多种方式创建集群,包括控制台、命令行界面CLI)和基础设施即代码IaC)。集群还可以应用不同的配置,包括网络、存储和应用程序配置。本章将重点介绍构建集群的先决条件以及构建集群所需的基本配置。具体来说,我们将涵盖以下主题:

  • 理解构建 EKS 集群的先决条件

  • 理解 EKS 集群的不同配置选项

  • 列举自动化选项

  • 创建您的第一个 EKS 集群

让我们从创建第一个集群前需要做的工作开始。

技术要求

您应该对云自动化有所了解,最好熟悉 CloudFormation,并且具备一定的编程语言或软件开发经验。

为了跟上进度,您还需要一个 AWS 账户来启动 EKS 资源。如果您没有账户,请前往 AWS 并创建一个:aws.amazon.com/

重要说明

这些活动将导致 AWS 费用,因此在构建完资源后,请确保删除所有资源。

理解构建 EKS 集群的先决条件

默认情况下,用于创建 AWS 账户的电子邮件地址和密码属于根用户,并且它们拥有在 AWS 账户中执行任何操作的权限。AWS 的最佳实践是为此账户启用多因素身份验证MFA),并且仅在紧急情况下使用此账户。

在创建 EKS 集群之前,您需要在拥有 AWS 账户和根用户访问凭证后执行以下活动:

  1. 使用临时根凭证配置您的 AWS CLI 环境。

  2. 作为根用户,您应该:

    1. 创建 EKS 管理员策略,使用最小权限来部署和管理 EKS 集群

    2. 创建一个 EKS 集群管理员组并将 EKS 管理员角色分配给该组

    3. 创建一个新用户并将其添加到 EKS 集群管理员组

    4. 创建访问凭证并将其添加到您的 AWS CLI 配置中

  3. 使用以下指南在您的工作站上安装 kubectl:docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html

使用临时根凭证配置您的 AWS CLI 环境

通常,您只需运行 $ aws configure 命令,它会要求您输入默认的访问凭证、区域和输出格式,但我们不希望永久保存根凭证,因此我们将使用环境变量暂时保存它们:

export AWS_ACCESS_KEY_ID=<root access key>
export AWS_SECRET_ACCESS_KEY=<root secret access key>
export AWS_DEFAULT_REGION=<working region>

创建 EKS 管理员策略

为 EKS 管理员提供正确权限的最简单方法是授予他们访问 AWS 管理的 AdministratorAccess 管理角色的权限。你可以使用以下命令获取角色的唯一身份 AWS 资源名称 (ARN):

$ export EKSARN=$(aws iam list-policies --query 'Policies[?PolicyName==`AdministratorAccess`].{ARN:Arn}' --output text)

AdministratorAccess 角色非常宽泛,允许资源获得分配的权限和大量不必要的权限。理想情况下,创建的 EKS 管理员角色应该按照最小权限安全模型定义,减少权限。创建此角色相当复杂,因为它需要多个资源权限。以下表格列出了 EC2、EKS、KMS 和 IAM 所需的最小权限。然而,如果你需要创建 VPC/子网等,可能需要向此角色添加权限。

AWS 资源 最小 权限集
EC2 API "ec2:RunInstances", "ec2:RevokeSecurityGroupIngress", "ec2:RevokeSecurityGroupEgress", "ec2:DescribeRegions", "ec2:DescribeVpcs", "ec2:DescribeTags", "ec2:DescribeSubnets", "ec2:DescribeSecurityGroups", "ec2:DescribeRouteTables","ec2:DescribeLaunchTemplateVersions", "ec2:DescribeLaunchTemplates", "ec2:DescribeKeyPairs", "ec2:DescribeInternetGateways", "ec2:DescribeImages", "ec2:DescribeAvailabilityZones", "ec2:DescribeAccountAttributes", "ec2:DeleteTags", "ec2:DeleteSecurityGroup", "ec2:DeleteKeyPair", "ec2:CreateTags", "ec2:CreateSecurityGroup", "ec2:CreateLaunchTemplateVersion", "ec2:CreateLaunchTemplate", "ec2:CreateKeyPair", "ec2:AuthorizeSecurityGroupIngress", "ec2:AuthorizeSecurityGroupEgress"
EKS API "eks:UpdateNodegroupVersion", "eks:UpdateNodegroupConfig", "eks:UpdateClusterVersion", "eks:UpdateClusterConfig", "eks:UntagResource", "eks:TagResource", "eks:ListUpdates", "eks:ListTagsForResource", "eks:ListNodegroups", "eks:ListFargateProfiles", "eks:ListClusters", "eks:DescribeUpdate", "eks:DescribeNodegroup", "eks:DescribeFargateProfile", "eks:DescribeCluster", "eks:DeleteNodegroup", "eks:DeleteFargateProfile", "eks:DeleteCluster", "eks:CreateNodegroup", "eks:CreateFargateProfile", "eks:CreateCluster"
KMS API "kms:ListKeys"
IAM API "iam:PassRole", "iam:ListRoles", "iam:ListRoleTags", "iam:ListInstanceProfilesForRole", "iam:ListInstanceProfiles", "iam:ListAttachedRolePolicies", "iam:GetRole", "iam:GetInstanceProfile", "iam:DetachRolePolicy", "iam:DeleteRole", "iam:CreateRole", "iam:AttachRolePolicy"

表 3.1 – EKS 管理员示例权限

一旦你设置了所需的权限集,你就可以创建一个策略文档。下面的 JSON 示例只包含了 KMS 权限,便于简化展示:

{    "Version": "2012-10-17",
    "Statement": [
        {"Sid": "KMSPermisssions",
            "Effect": "Allow",
            "Action": ["kms:ListKeys"],
            "Resource": "*"
        }]}

使用 aws iam create-policy --policy-name bespoke-eks-policy --policy-document file://<mypolicyfile.json> 命令根据你创建的 JSON 文件创建 IAM 策略,然后你可以使用以下命令检索 ARN:

$ export EKSARN=$(aws iam list-policies --query 'Policies[?PolicyName==`bespoke-eks-policy`].{ARN:Arn}' --output text)

创建 EKS 管理组

使用 CLI 创建组是非常简单的,使用以下命令:

$ aws iam create-group --group-name EKS-Admins.

然后,你需要使用以下命令附加在前一步中创建的策略:

$ aws iam attach-group-policy --policy-arn $EKSARN --group-name EKS-Admins.

创建一个新用户

现在我们已经拥有了权限,并且组已经创建,我们可以使用以下命令创建一个新用户并将其分配到该组:

$ aws iam create-user --user-name <MYUSERNAME>

然后,你可以使用以下命令将刚创建的用户添加到组中:

$ aws iam add-user-to-group --user-name <MYUSERNAME> --group-name EKS-Admins

你还需要使用以下方法创建密码:

$ aws iam update-login-profile --user-name <MYUSERNAME> --password <password>

现在你需要创建访问凭证并使用以下方法存储它们:

$ aws iam create-access-key --user-name <MYUSERNAME>
{ "AccessKey": {
        "UserName": "<MYUSERNAME>",
        "Status": "Active",
        "CreateDate": "2022-08-19T11:01:07Z",
        "SecretAccessKey": "67ghjghjhjihk",
        "AccessKeyId": "hgjgjgjhgjhgj"}}

你应该复制此命令输出的凭证(SecretAccessKeyAccessKeyId),使用 $aws configure 命令将它们添加到你的 CLI 配置中,并在接下来的示例中使用此账户/凭证。

重要提示

你仍然需要授予控制台访问权限。请参阅此链接:docs.aws.amazon.com/IAM/latest/UserGuide/console_controlling-access.html

你还应该启用 MFA。请参阅此链接:docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa.html

现在我们已经具备了所有前提条件,我们需要考虑如何配置 EKS 集群。

了解 EKS 集群的不同配置选项

Kubernetes 默认是可扩展的,这是它如此受欢迎的原因之一。除了我们已经讨论过的标准 API 对象,如 PodsServices,你还可以扩展 API 来支持自定义资源、控制器、操作器以及用于网络和存储的标准插件。所有这些元素都可以作为集群创建过程的一部分添加到 EKS 集群中;然而,在本章中,我们将介绍如何配置基本的设置,以便启动并运行一个简单的集群。下表定义了将要配置的内容,并提供了指向其他章节的链接,后者展示了更多的配置步骤:

EKS 配置域 描述
控制 plane 正如我们所提到的,EKS 实际上是一个托管的控制 plane,所以这必须始终完成,接下来的部分将重点讲解如何创建它。
基本网络配置 我们将在本节中简要介绍使用默认 AWS EKS VPC 插件(CNI)的配置,但在 第七章中会有更详细的讲解。
基础节点组 在下一部分,我们将使用 EC2 资源创建一个小的节点组,用于托管关键的集群资源,如 VPC 网络插件。
Pod 存储服务 我们不会在本节中讨论这一点,但将在第十二章中详细介绍。
额外节点组 我们不会在本节中讨论这一点,但将在第八章中详细介绍。
Fargate 配置文件 我们不会在本节中讨论这一点,但将在第十五章中详细介绍。
Kubernetes 应用程序 我们不会在本节中讨论这一点,但将在第十一章第十三章第十四章中详细介绍。
高级网络概念 我们不会在本节中讨论这一点,但将在第八章中详细介绍。
服务网格 我们不会在本节中讨论这一点,但将在第十六章中详细介绍。

表 3.2 – EKS 配置区域

在我们实际创建一个由托管控制平面、基本网络和基础节点组组成的集群之前,先回顾一下我们可以选择的不同方式来部署和自动化它,以及为什么会选择某种方式而不是其他方式。

枚举自动化选项

以下图示(图 3.1)展示了 AWS 中基础设施自动化的发展历程。大多数用户最初是通过使用 playbooks 或维基和 AWS 控制台进行手动配置。这样做的挑战是很难重复,如果需要更改或添加某些内容,则需要手动操作。

下一步是使用 Shell 脚本来自动化 AWS 资源的部署,例如,使用 AWS CLI。这样并不完美,因为如果你运行相同的命令两次,可能会得到不同的结果。因此,AWS CLI 并不是(必然)幂等的。所以,在 2011 年,AWS 发布了 CloudFormation,这是一个 IaC 框架,可以安全地创建基础设施资源。

图 3.1 – 自动化选项

图 3.1 – 自动化选项

IaC 已成为部署 AWS 资源的最佳实践,2014 年 HashiCorp 发布了 Terraform,它变得非常流行,再次允许你安全地自动化和部署 AWS 资源。CloudFormation 和 Terraform 的挑战在于,它们各自有自己的标记语言,需要学习,并且可能比较复杂。

多年来,已经开发出各种生成器,允许你无需理解如何编写脚本就能创建 CloudFormation 和 Terraform 脚本。这个概念进一步扩展为抽象,如 AWS Cloud Development KitCDK),它允许你使用常规编程语言(如 Python、TypeScript 等)生成和部署 CloudFormation。

在 Kubernetes 中,这种额外的抽象层是集群的一部分,清单、Helm 图表和 Kustomize 被用来抽象 Kubernetes 资源,工具如 eksctl 提供了一个简单的接口来配置 EKS 集群。

我应该使用哪种自动化工具/框架?

对于任何自动化操作,一条常规的经验法则是 不要重复自己 (DRY),所以如果你打算定期创建或删除集群,使用自动化,并尽可能使用最高级别的抽象。像 CDK 和 eksctl 这样的工具意味着你不需要学习 CloudFormation,但仍然可以依赖 安全 的部署实践,如声明式配置和幂等操作。

Terraform 支持这些 安全 操作,这意味着你还可以支持其他云服务,如 Microsoft Azure 和 Google Cloud Platform,以及其他本地资源。

在接下来的部分中,我们将向你展示如何使用控制台和 AWS CLI 创建一个基本的集群,然后通过以下 IaC 工具简化操作:Terraform、eksctl 和 AWS CDK。

创建你的第一个 EKS 集群

请使用以下命令验证你是否使用了在前提条件中创建的用户名的凭证:

$ aws sts get-caller-identity
{    "UserId": <MYUSERNAME>",
    "Account": "11112222333",
    "Arn": "arn:aws:sts::11112222333<MYUSERNAME>/IAM_ROLE>"}

选项 1:使用 AWS 控制台创建 EKS 集群

要开始此练习,请打开浏览器,访问 aws.amazon.com/,并使用你在前提条件中创建的用户名/凭证登录到你的帐户。

登录后,完成以下步骤:

  1. 在搜索框中输入 IAM,然后从搜索结果中选择 IAM | 角色

  2. 现在,你应该通过点击 创建角色 按钮来创建一个集群服务角色,这将允许集群调用其他 AWS 服务。这是一个简单的策略,定义在 docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html 中,应该与 AmazonEKSClusterPolicy 管理策略关联。

  3. 创建服务角色后,选择你希望启动 Amazon EKS 集群的区域,在搜索框中输入 EKS,并选择 弹性 Kubernetes 服务

图 3.2 – 选择 EKS

图 3.2 – 选择 EKS

  1. 在 EKS 启动屏幕上,点击 添加集群 | 创建 按钮。

图 3.3 – 添加集群

图 3.3 – 添加集群

  1. 在弹出的屏幕上,在 名称 字段中输入集群名称,从 Kubernetes 版本 字段中选择你要部署的 Kubernetes 版本,选择在 第 2 步 中创建的服务角色,然后点击 下一步

图 3.3 – 配置集群

图 3.3 – 配置集群

  1. 在 EKS 网络屏幕的第一个面板中,你需要选择控制平面将使用的 VPC 和子网,以及任何工作节点使用的安全组。如果这些资源不存在,你可以使用 VPC 控制台链接添加它们(确保在浏览器标签页或窗口中打开该链接)。

图 3.4 – 输入 VPC 和安全组详细信息

图 3.4 – 输入 VPC 和安全组详细信息

  1. 在下一个面板中,选择集群端点的类型。在本示例中,我们将保持默认的公共选项,这意味着集群 API 可以通过互联网访问。

图 3.5 – 集群端点

图 3.5 – 集群端点

  1. 在最后一个网络配置面板中,您可以保留默认值,这些值与步骤 5中选择的 Kubernetes 版本相关,然后点击下一步

图 3.6 – 完整的网络配置部分

图 3.6 – 完整的网络配置部分

  1. 通过选择按钮并点击下一步,启用将审计日志记录到 CloudWatch 日志。

图 3.7 – 控制平面日志记录

图 3.7 – 控制平面日志记录

  1. 审查您的集群设置并点击创建按钮。此过程需要 20-30 分钟才能完成,涉及在 AWS 所有的 VPC 中设置控制平面(API 和 etcd 服务器),并通过弹性网络接口ENIs)将其连接到您的 VPC。完成后,您将看到一个状态为活动的新集群,如下图所示:

图 3.8 – 活动集群

图 3.8 – 活动集群

  1. 由于这是一个公共集群,您可以运行aws eks update-kubeconfig --cluster <CLUSTERNAME> --region <YOURREGION>命令来更新您的kubeconfig文件。我们已经创建了 EKS 控制平面并设置了网络,但目前我们没有任何节点连接到它。我们可以使用kubectl get nodes命令来验证这一点。您应该会收到未找到资源的消息。

  2. 如果点击集群名称链接(在步骤 10中为mycluster),您将进入下图所示的集群配置页面。点击计算选项卡,然后点击添加节点 按钮。

图 3.9 – 计算

图 3.9 – 计算

  1. 在第一个节点组配置面板中,为节点组输入名称和 EC2 工作节点 IAM 角色。如果该角色不存在,您可以通过 VPC 控制台链接添加它(确保在浏览器标签页或窗口中打开该链接)。角色应根据以下链接创建:docs.aws.amazon.com/eks/latest/userguide/create-node-role.html

图 3.10 – 节点组配置

图 3.10 – 节点组配置

  1. 您可以接受使用 EKS 优化的 Amazon Linux 操作系统OS)镜像在自动扩展组中启动 t3.medium EC2 实例的所有默认设置。点击下一步

  2. 选择您将在 VPC 中用于 EC2 工作节点的子网;您应该选择至少两个子网/可用区。点击下一步

图 3.11 – 节点组网络配置

图 3.11 – 节点组网络配置

  1. 审查您的节点组设置,然后点击kubeletkubeproxy,并重新连接到控制平面。一旦作业完成,节点组应为活跃,并且两个 EC2 实例应为准备就绪

图 3.12 — 活跃节点组

图 3.12 — 活跃节点组

  1. 我们可以使用kubectl get nodes命令来验证这一点。输出现在应该显示您刚刚创建的两个节点。

  2. 完成集群设置后,请删除节点组,然后您可以删除集群。

重要提示

由于 AWS 控制台界面可能发生更改,如果您有任何问题,您可以随时查阅 AWS 文档,以获取有关创建 EKS 集群(docs.aws.amazon.com/eks/latest/userguide/create-cluster.html)和创建托管节点组(docs.aws.amazon.com/eks/latest/userguide/create-managed-node-group.html)的最新步骤。

在本小节中,我们已经创建了一个带有托管控制平面的基础集群、一个 AWS VPC 网络和一个包含两个 EC2 工作节点的基础节点组。在下一小节中,我们将看到如何使用 AWS CLI 创建相同的集群。

选项 2:使用 AWS CLI 创建 EKS 集群

AWS CLI 是一个用于管理 AWS 资源的工具。您可以通过此链接安装它:docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

要使用 AWS CLI 创建 EKS 集群,请按照此处详细说明的步骤操作:

  1. 我们将重用集群服务角色和选项 1中使用的相同子网。如果它们不存在,请按照选项 1中的步骤创建它们。

  2. 然后,您可以使用您在先决条件中创建的用户名/凭证,使用以下命令在您的账户中创建托管控制平面。子网和安全组可以与选项 1中使用的相同:

    $ aws eks create-cluster --region <MYREGION> --name mycluster --kubernetes-version 1.22 --role-arn <MYSERVICEROLEARN> --resources-vpc-config subnetIds=subnet-1,subnet-2,securityGroupIds=sg-a
    
  3. 现在,这将需要 20 到 30 分钟完成,并涉及在 AWS 拥有的 VPC 中设置控制平面(API 和 etcd 服务器),并通过 ENI 将其连接到您的 VPC。当它完成时,您将看到一个状态为活跃的新集群,如以下截图所示。

图 3.13 – 活跃集群

图 3.13 – 活跃集群

  1. 同样,我们已经创建了 EKS 控制平面并设置了网络,但目前我们没有任何节点连接到它。我们可以通过更新kubeconfig文件后使用kubectl get nodes命令来验证此情况。您应该会看到未找到资源消息。

  2. 您可以使用以下命令创建基础节点组,该命令将使用 EKS 优化的 Amazon Linux 操作系统镜像并使用在选项 1中创建的 EC2 角色,在自动扩展组中创建两个 t3.medium EC2 实例。

    $ aws eks create-nodegroup --region <MYREGION>   --cluster-name mycluster --nodegroup-name basicCLI --scaling-config minSize=2,maxSize=2,desiredSize=2 --subnets subnet-1  subnet-2 --ami-type AL2_x86_64 --node-role <EKSWORKERNODEROLEARN>
    
  3. 这将需要 10 到 20 分钟,并将创建两个 EC2 实例,配置 Kubernetes 代理(kubeletkubeproxy),并与控制平面连接。任务完成后,节点组应显示为 Active,两个 EC2 实例应显示为 Ready

图 3.14 – 一个 CLI 节点组

图 3.14 – 一个 CLI 节点组

  1. 我们可以通过使用 kubectl get nodes 命令来验证这一点。输出现在应该显示你刚刚创建的两个节点。

  2. 一旦你完成了集群的操作,删除节点组后,你可以使用以下命令删除集群:

    $ aws eks delete-nodegroup --cluster-name <CLUSTER_NAME> --nodegroup-name <NODEGROUP_NAME> --region <REGION>
    $aws eks delete-cluster --name <CLUSTER_NAME> --region <REGION>
    

如你所见,这个过程比通过 AWS 控制台点击操作要简单得多。这些命令可以放入一个 shell 脚本中,并且你可以使用环境变量来参数化输入,例如更改集群名称。然而,并不能保证每个命令都能安全地重复执行,如果出现失败,脚本执行可能会出现问题。一种更好的方法是使用 IaC,我们将在接下来的子节中探讨这一点。

选项 3:使用 Terraform 创建 EKS 集群

Terraform 是由 HashiCorp 创建的一个开源项目,它由一个单一的二进制文件组成,可以用来验证、部署和删除(销毁)AWS 基础设施资源。你可以按照learn.hashicorp.com/tutorials/terraform/install-cli 上的说明来安装 Terraform。

为了使用 Terraform 创建你的第一个 EKS 集群,我们将克隆 HashiCorp 提供的官方示例,包含用于创建 EKS 集群的配置文件。这是一个非常完整的解决方案(创建 53 个资源),将创建一个新的 VPC、两个托管工作节点组,以及所有相关的角色和权限。使用以下命令克隆并进入克隆目录:

$ git clone https://github.com/hashicorp/learn-terraform-provision-eks-cluster
$ cd learn-terraform-provision-eks-cluster

Terraform 通过将所有 .tf 文件汇总为一个单一的配置文件,然后使用你的本地凭证将其部署到 AWS。你需要修改某些 .tf 文件以适应你的 AWS 账户;下表解释了哪些文件需要修改:

Terraform (.****tf) 文件 更改
./``vpc.tf 将区域变量更改为你想使用的区域,例如:variable "``region" {  default     = "``eu-central-1"  description = "``AWS 区域"``}
./``eks-cluster.tf 将 EKS 版本更改为所需版本,例如:module "``eks" {        cluster_version = "``1.22" }

表 3.3 – Terraform 更改

现在你已经对 Terraform 文件进行了更改,可以使用 Terraform 创建 EKS 集群和环境。你应该按照这里详细描述的步骤进行操作:

  1. 运行$ terraform init命令以创建本地状态并下载所有远程模块文件,例如用于创建新 VPC 的 VPC 模块。下载所有模块文件可能需要一些时间。如果此命令成功,您将看到Terraform 已经成功 初始化!消息。

  2. 运行$ terraform plan命令查看在实际部署资源之前将要创建的资源。这是与控制台和 CLI 方法相比的一个主要优势,因为您还可以使用此命令查看当您更改.tf文件时会发生什么变化。

  3. 运行$ terraform apply -auto-approve命令来创建/部署资源。这将需要 20 到 30 分钟完成(所以请坐好)。完成后,您将拥有一个新的 IAM 角色,一个位于新 VPC 中的 EKS 集群,新的互联网和 NAT 网关,以及两个托管节点组和三个 EC2 实例。

  4. 在这个选项中,我们实际上创建了托管节点组,我们可以通过使用$ kubectl get nodes命令来验证它们。在更新kubeconfig文件后,您将看到三个工作节点。

  5. 完成集群后,使用$ terraform destroy -auto-approve命令删除所有资源。Terraform 将自动确定删除的顺序。

如您所见,Terraform 是一个非常强大的工具,简化了您配置和部署资源的方式。您仍然需要配置或创建 Terraform 模块,这需要您学习 Terraform 语法和标记语言。

在下一个子章节中,我们将看到如何使用 eksctl 创建 EKS 集群,在幕后使用 CloudFormation,而无需学习任何 CloudFormation 语法。

选项 4:使用 eksctl 创建 EKS 集群

eksctl 是一个开源项目,托管在 GitHub 上(github.com/weaveworks/eksctl),由 Weaveworks 和 AWS 共同开发。它类似于 Terraform,以单个二进制文件运行并创建 AWS 资源;但是,它只能用于创建、更新和管理 EKS 集群(以及任何相关的资源)。

您可以按照docs.aws.amazon.com/eks/latest/userguide/eksctl.html上的说明安装 eksctl。安装二进制文件到您的工作站后,您可以使用eksctl info命令测试安装。创建集群的最简单方法是运行以下命令:

$ eksctl create cluster --name mycluster --region eu-central-1

这种方式与 Terraform 类似,它将创建 VPC、EKS 集群、一个节点组以及所有相关的资源,例如 IAM 角色。在后台,eksctl 使用 CloudFormation,并将创建两个 CloudFormation 堆栈,来创建(29+)个 AWS 资源。如果您运行$ aws cloudformation list-stacks命令,您将看到名为eksctl-xx的堆栈,它们部署所有 EKS 资源:一个用于主资源(包括 VPC),另一个专门用于节点组。CloudFormation 堆栈管理资源的状态,还可以用来检测漂移并进行更改。

再次使用此选项,我们已创建了托管节点组,可以通过使用kubectl get nodes命令验证它们。更新kubeconfig文件后,您将看到两个工作节点。

您还可以通过添加命令行选项来修改默认配置。以下命令将更改作为默认节点组一部分正在部署的实例类型和数量:

$ eksctl create cluster --name mycluster --instance-types m5.xlarge --nodes 2 --region eu-central-1

eksctl 支持多种集群创建选项。您可以使用--help标志列出其他支持的选项,以获取更多详细信息。

完成集群配置后,您可以使用$ eksctl delete cluster --name mycluster --region eu-central-1命令删除所有资源。由于 eksctl 是一个特定的 EKS 配置工具,因此它具有一些内置功能,例如节点排空。

如您所见,eksctl 为 Terraform 提供了更高层次的抽象,但由于它是一个 EKS 工具,因此不如 Terraform 灵活。在下一小节中,我们将简要介绍 AWS CDK,它使用像 Python 这样的编程语言,完全摆脱了任何 IaC 标记语言。

选项 5:使用 CDK 创建您的 EKS 集群

AWS CDK 类似于 Terraform 和 eksctl,它通过一组二进制文件来部署 AWS 基础设施。它在后台使用 CloudFormation,但与 eksctl 相比,它有四个主要优势:

  • IaC 代码使用标准编程语言编写,如 Python、TypeScript、Golang 等,因此开发人员可以在不学习 Terraform 或 CloudFormation 标记语言的情况下编写代码。

  • 您可以利用现有的语言控制结构,如IF-THEN-ELSEFOR循环等,以及现有的库,将复杂逻辑构建到您的 IaC 脚本中。

  • 您还可以创建与 EKS 无关的资源,如 DynamoDB。

  • 可以使用标准语言工具,如 pylint 或 pytest,测试和校验模板。

CDK 的详细探索超出了本书的范围(事实上,它可以成为一本全新的书)。如果您想深入了解 CDK,cdkworkshop.com/是一个很好的资源。相反,以下表格显示了创建 EKS 集群所需的基本命令:

Python 行 描述
my_vpc = ec2.Vpc.from_lookup(self,"clusterVPC",vpc_id=params['VPC']) 这行代码使用 CDK 中的 ec2.Vpc 对象,通过 params['VPC'] 字典从 AWS 账户中检索 VPC 详情。
eks_master_role = iam.Role.from_role_arn(self,"iderole",params['IDEROLE']) 这行代码使用 CDK 中的 iam.Role 对象,通过 params['IDEROLE'] 字典从 AWS 账户中检索角色详情,作为集群的主要管理员角色。
security_group = ec2.SecurityGroup.from_lookup_by_id(self,"idesg",params['IDESG']) 这行代码使用 CDK 中的 iam.SecurityGroup 对象,通过 params['IDESG'] 字典从 AWS 账户中检索现有的安全组,作为集群的附加安全组。
my_subnets=[]``for subnet in params['SUBID']:``my_subnets.append(ec2.Subnet.from_subnet_id(self,f"1{subnet.split('-')[1]}",subnet_id=subnet)) 这些代码行创建了一个空的子网列表,然后使用标准的 FOR 循环遍历存储在 params['SUBID'] 中的子网 ID 列表,通过 CDK 中的 ec2.Subnet 对象创建子网对象,并将其添加到子网列表中。
eks.Cluster(self,params['CLUSTERNAME'],``masters_role=eks_master_role,``security_group=security_group,``version=eval(f"eks.KubernetesVersion.{params['VERSION']}"),vpc=my_vpc,``vpc_subnets=my_subnets,``endpoint_access= eval(f"eks.EndpointAccess.{params['CLUSTERTYPE']}")) 这行代码将使用前面几行中获取的角色、VPC 和子网来创建一个集群,并从 params Python 字典中设置端点类型和版本,该字典包含了所有的配置信息。

表 3.4 – Python EKS CDK 示例

一旦代码编写完成,CDK 二进制文件可以用于执行以下操作:

  • 使用 cdk bootstrap 命令初始化 AWS 区域进行 CDK 部署

  • 使用 cdk synth 命令创建 CloudFormation 模板

  • 使用 cdk diff 命令了解将要部署或更改的内容

  • 使用 cdk deploy 命令创建并部署 CloudFormation 模板

CDK 提供了所有部署工具中最高级别的抽象,因此应当作为 EKS 自动化和部署的一个良好起点,但像 eksctl 和 Terraform(Terraform 也有 CDK 变体)这样的工具也提供了很好的选项。本节概述了部署基本 EKS 集群的不同方式。

总结

在本章中,我们讨论了配置和部署一个基本的 Amazon EKS 集群之前需要的前提条件,如设置部署用户。我们回顾了在 EKS 中需要配置的不同 EKS 配置和自动化选项,以及可用的框架和工具。

然后,我们逐步介绍了五种选项,从使用 AWS 控制台和 CLI 到不同的 IaC 框架,来创建一个基本的 EKS 集群。

完成本课程后,你已经学会了如何配置你的 EKS 集群,并且在 AWS 账户下拥有一个正在运行的集群,同时也安装了 kubectl 和 AWS CLI,以便与 EKS 集群进行交互。

在下一章中,我们将开始学习如何在 Amazon EKS 上部署和运行容器化应用程序。

进一步阅读

第四章:在 EKS 上运行你的第一个应用程序

在前面的章节中,我们讨论了如何配置和构建基本集群。在本章中,我们将探讨如何在该集群上部署我们的第一个应用程序。

Kubernetes 的流行程度部分源于其灵活的构建和部署服务及应用程序的方式,以及如何利用 Kubernetes 的关键功能从故障中恢复并扩展应用程序的能力。在 2021 年的 CNCF 年度调查中,96% 的受访者表示他们正在使用或评估 Kubernetes。

在本章中,我们将介绍在 EKS 上部署简单应用程序的不同方式,以及可视化工作负载的工具。具体来说,我们将涵盖以下内容:

  • 了解应用程序的不同配置选项

  • 创建你的第一个 EKS 应用程序

  • 使用 AWS 管理控制台和第三方工具(例如 Lens)可视化你的工作负载

你应该熟悉 YAML、基础网络知识以及 EKS 架构。让我们首先确定在部署第一个应用程序之前需要完成的准备工作。

技术要求

在开始本章之前,请确保完成以下内容:

  • 你拥有一个 EKS 集群并且能够执行管理任务

  • 你有至少两个工作节点连接到集群

  • 你与 EKS API 端点之间有网络连接

  • 已在工作站上安装 AWS CLI 和 kubectl 二进制文件

了解应用程序的不同配置选项

Kubernetes 上的应用程序由一个或多个容器组成,分布在工作节点上,并通过不同的方法暴露到集群外部。以下表格定义了将配置的内容,并提供了指向其他章节的地图,展示了额外的配置步骤:

应用程序 配置领域 描述
单个 Pod 在此示例中,可以从支持的仓库镜像中拉取单个 Pod,并部署到特定的命名空间。
弹性部署 在此示例中,将使用 Kubernetes 部署在不同的工作节点上部署多个 Pods,调度器将保持所需的数量。
更新 你的部署 在此示例中,部署容器镜像被更新,新的镜像将遍布整个部署。
外部服务 在此示例中,部署将作为简单的节点端口服务进行暴露。
入口控制器 在此示例中,部署将使用 NGINX 入口控制器进行暴露,从而提供更高的访问控制。
多容器 Pod 通常使用 sidecar 进行健康检查或服务网格。这将在第十六章中详细讨论。
负载均衡器 这将在第十四章中详细讨论。
自动扩展 Pods 这将在第十八章中讨论。
Pods 的存储 这在 第十二章 中讨论。

表 4.1 – 应用配置区域

现在让我们看看将第一个应用部署到 EKS 所需的内容。

引入 kubectl 配置

kubectl 是一个 Kubernetes 命令行管理工具,允许用户与 Kubernetes API 服务器进行交互,并执行任何管理任务,包括部署、更新或删除应用(前提是他们有权限执行这些操作)。

为了与集群通信,集群的详细信息,如 API 端点 DNS 名称和服务器证书,都需要添加到kubeconfig文件中。可以使用以下命令(你需要安装 AWS CLI)来更新配置文件,该文件通常存储在$HOME/.kube中的config文件中:

$ aws eks update-kubeconfig --name mycluster  --region eu-central-1

重要提示

用于运行 CLI 命令的 AWS 用户需要具备 IAM 权限,才能成功执行此操作并访问 AWS EKS API。

该文件现在将在cluster部分包含一个新集群的引用,带有证书数据、API 端点(服务器)和名称。示例如下:

clusters:
- cluster:
    certificate-authority-data: xxxxxx
    server: https://hfjhf.gr7.eu-central-1.eks.amazonaws.com
    name: arn:aws:eks:eu-central-1:334:cluster/mycluster

它还会包含一个user部分。默认情况下,EKS 会使用 IAM 身份,因此没有实际的用户数据。相反,CLI 命令aws eks get-token(带有支持参数)用于获取身份令牌,该令牌用于 EKS 将 IAM 用户映射到 Kubernetes 身份(更多信息请参见 第六章)。在配置文件中看到的配置示例如下:

users:
- name: arn:aws:eks:eu-central-1:334:cluster/mycluster
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
      - --region
      - eu-central-1
      - eks
      - get-token
      - --cluster-name
      - education-eks-D20eNmiw
      command: aws
      env: null
      provideClusterInfo: false

最后,还会创建一个 Kubernetes context,它将clusteruser配置连接在一起。下面展示了一个示例:

contexts:
- context:
    cluster: arn:aws:eks:eu-central-1:334:cluster/mycluster
    user: arn:aws:eks:eu-central-1:334:cluster/mycluster
    name: arn:aws:eks:eu-central-1:334:cluster/mycluster

上下文允许在配置文件中配置多个集群和身份,并允许用户在它们之间切换。切换上下文可以通过使用kubectl config --kubeconfig=<CONFIGDIR> use-context <CONTEXT>命令或使用开源工具,如 github.com/ahmetb/kubectx

现在我们已经设置了与集群通信所需的基本配置。在下一节中,我们将使用kubectl进行一些基本的集群连接性验证,并部署我们的第一个 Pod。

使用 kubectl 验证连接性

验证是否与集群连接的最简单方法是使用kubectl version命令。你应该看到类似下面的输出:

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"22+", GitVersion:"v1.22.6-eks-7d68063", GitCommit:"f24e667e49fb137336f7b064dba897beed639bad", GitTreeState:"clean", BuildDate:"2022-02-23T19:32:14Z", GoVersion:"go1.16.12", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"20+", GitVersion:"v1.20.15-eks-18ef993", GitCommit:"77b5697130c2dea4087e1009638e21cc93f5c5b6", GitTreeState:"clean", BuildDate:"2022-07-06T18:04:29Z", GoVersion:"go1.15.15", Compiler:"gc", Platform:"linux/amd64"}

以下表格列出了你在运行此命令时可能遇到的一些错误及其解决方法:

错误输出 描述
无法连接到服务器:获取凭证时失败:exec:可执行的 aws 失败,退出 代码 253 在这种情况下,kubectl无法获取 AWS IAM 凭证以从 EKS API 请求令牌;请更新或添加 AWS 凭证到工作站。
无法连接到服务器:拨号 tcp 10.1.3.51:443: i/o 超时 在这种情况下,IP 地址是一个私有地址,kubectl 客户端无法访问它。此错误通常表示网络问题,例如 IP 路由问题或与客户端 IP 相关的防火墙/IP 白名单问题。
错误:必须登录到服务器(服务器要求客户端提供凭证) 在这种情况下,kubectl 有凭证并可以连接到服务器端点,但凭证没有权限获取版本信息。这是一个 RBAC 问题,通常意味着正在使用的 IAM 用户没有正确的 Kubernetes 权限。

表 4.2 – 常见的 kubectl 连接错误示例

kubectl 备忘单

kubectl 备忘单包含非常有用的内容,可以帮助你快速学习使用哪个 kubectl 命令。你可以在官方 Kubernetes 文档中学习常用的 kubectl 命令和标志:kubernetes.io/docs/reference/kubectl/cheatsheet/

现在我们已经验证了从 kubectl 到集群的连接,我们开始部署我们的第一个应用。

创建你的第一个 EKS 应用

Kubernetes 中的最低抽象层是 Pod,它表示一个或多个共享同一命名空间的容器。你可以选择在 Pod 中添加额外的容器,以提供额外的功能,例如服务网格或缓存。因此,虽然许多 Pod 仅包含一个容器,但你并不限于一个容器。

在接下来的章节中,我们将部署一个 Pod,然后在此基础上使用更高级的 Kubernetes 对象。作为开发者或 DevOps 工程师,你将花费大量时间构建和部署应用,因此理解你需要做的事情非常重要。

使用 kubectl 命令在 Amazon EKS 上部署你的第一个 Pod

你可以使用 kubectl run 命令快速部署并将 CLI 会话附加到 Pod,命令如下:

$ kubectl run -it busybox --image=busybox --restart=Never

当你执行此命令时,会发生一些事情,但在我们回顾之前,让我们先看看通过 kubectl run busybox --image=busybox --restart=Never --dry-run=client -o yaml 命令创建的清单,它显示了正在创建的 API 对象/类型,不会将其发送到 Kubernetes API。命令的输出如下:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: busybox
  name: busybox
spec:
  containers:
  - image: busybox
    name: busybox
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
status: {}

如我们所见,清单定义了一个 Pod 规格,包含 namebusybox 镜像(将从公共仓库中拉取)和 restartPolicy,这意味着一旦完成,调度器不会尝试重新启动它。

部署过程如下:

  1. kubectl run 命令将创建 Pod 的清单并将其提交到 Kubernetes API。

  2. API 服务器将持久化 Pod 规格。

  3. 调度器将接收新的 Pod 规格,审查它,并通过筛选和评分的过程,选择一个工作节点将资源部署到该节点,并将 Pod 规格标记给这个节点。

  4. 在节点上,kubelet 代理监控集群数据存储(etcd),如果发现有新的 Pod 规格,代理将使用该规格在节点上创建 Pod。

  5. 一旦 Pod 启动,你的 kubectl 会话将连接到 Pod(正如我们使用-it标志所指定的)。你将能够使用 Linux 命令与 Pod 进行交互。你可以通过输入exit离开会话。

一旦你退出会话,你可以如下验证 Pod 的状态:

$ kubectl get pods
NAME      READY   STATUS      RESTARTS   AGE
busybox   0/1     Completed   0          20s

重要提示

Pod 的状态为Completed,因为我们指定了restartPolicy: Never,所以一旦交互会话结束,容器将不再可访问。你可以使用$ kubectl delete pod busybox命令删除 Pod。

在下一节中,我们将看到如何将 Pod 的概念扩展到 Deployment。

使用 Kubernetes Deployment 部署一个 Pod

Deployment 在 Pod 之上增加了另一层抽象;它允许你部署 Pod 规格,并支持对这些 Pods 进行扩展以及更新 Pod 镜像。相比基本的 Pod 规格,Deployment 可以更高效地管理应用程序的生命周期。以下 Deployment 清单将用于部署两个运行版本 1.34.1 的 BusyBox 的 Pods。我们还包括一个简单的命令execute sleep 3600,它会让容器保持活动状态 3,600 秒:

chapter4-deployment.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: simple-deployment-app
  template:
    metadata:
      labels:
        app: simple-deployment-app
    spec:
      containers:
        - name: busybox
          image: busybox:1.34.1
          command:
            - sleep
            - "3600"

你可以使用$ kubectl create -f chapter4-deployment.yaml命令来创建 Deployment。你还会看到响应中出现deployment.apps/busybox-deployment created消息。

你可以使用$ kubectl get deployment simple-deployment命令验证 Deployment;以下是一个示例输出:

NAME                READY   UP-TO-DATE   AVAILABLE   AGE
simple-deployment   2/2     2            2           106m

Deployment 是一个复合类型,它包含 Deployment 本身、Pods 和一个ReplicaSet,该 ReplicaSet 用于保持每个 Deployment 中两个 Pods 的期望状态。你可以使用kubectl get all命令来获取当前命名空间中的所有资源。以下是一个示例输出:

$ kukectl get all
…………
NAME                         READY   STATUS    RESTARTS   AGE
pod/simple-deployment-123-5mbpb   1/1     Running   1          108m
pod/simple-deployment-432-74kxf   1/1     Running   1          108m
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/simple-deployment   2/2     2     2      108m
NAME                        DESIRED   CURRENT   READY   AGE
replicaset.apps/simple-deployment-6995f6966   2  2 2  108m

Deployment 提供了一种便捷的方式来进行变更。让我们看看如何修改这个 Deployment。

修改你的 Deployment

现在我们有了一个 Deployment,我们可以使用kubectl scale deployment simple-deployment --replicas=3命令来扩展它,这将把期望的 Pod 数量增加到三,从而添加另一个 Pod。

我们还可以使用kubectl set image deployment simple-deployment busybox=busybox:1.35.0命令更新 Deployment 镜像,这将触发滚动更新(默认机制)。

你可以使用kubectl rollout status命令验证部署状态:

$ kubectl rollout status deployment/simple-deployment
deployment "simple-deployment" successfully rolled out

你会看到,Pods 都被新版本替换,你可以使用 $ kubectl exec --stdin --tty <POD ID> -- /bin/sh 命令进入 Pod shell (/bin/sh),然后在 Pod shell 中运行 busybox |head -``1 命令。

接下来,让我们看看如何让这个部署对集群外部的用户可见。

暴露你的 Deployment

虽然我们已经通过 Deployment 部署了 Pods,但为了让其他 Pods/Deployments 能与这些 Pods 通信,它们必须使用 Pods 的 IP 地址。一种更好的方法是通过使用服务来暴露这些 Pods,这意味着其他集群 Pods 或外部系统可以使用该服务,Kubernetes 会对所有可用 Pods 进行负载均衡。接下来是一个服务的示例:

chapter4-basic-service.yaml

---
apiVersion: v1
kind: Service
metadata:
   name: myapp 
spec:
   type: ClusterIP
   ports:
     - protocol: TCP
       port: 80
       targetPort: 9376
   selector:
     app: simple-deployment-app

我们创建的服务是一个 ClusterIP 服务,这意味着它只能从集群内部访问。它会暴露 80 端口,并将其映射到任何具有 app=simple-deployment-app 标签的 Pod 的 9376 端口(即我们通过 Deployment 创建的 Pods)。

我们可以使用 kubectl get service 命令来验证服务:

$ kubectl get svc -o wide
NAME  TYPE  CLUSTER-IP EXTERNAL-IP   PORT(S)   AGE   SELECTOR
myapp  ClusterIP   172.20.124.66   <none>   80/TCP    16m   app=simple-deployment-app

如果我们通过 kubectl describe service myapp 命令更深入地查看服务,可以看到 Endpoints 配置项,其中包含了具有 app=simple-deployment-app 标签的 Pods 的 IP 地址。我们可以通过 kubectl get po -o wide 命令验证这一点,示例如下:

$ kubectl describe service myapp
Name:              myapp
Namespace:         default
……………………
Selector:          app=simple-deployment-app
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                172.20.124.66
…………………..
Endpoints:  10.1.3.27:9376,10.1.30.176:9376,10.1.30.38:9376
Session Affinity:  None
Events:            <none>
$ k get po -o wide
NAME  READY   STATUS    RESTARTS   AGE   IP  NODE  NOMINATED NODE   READINESS GATES
simple-deployment-111-5gq92   1/1     Running   0   52m   10.1.30.38 ip-3.eu-central-1.compute.internal   <none>     <none>
simple-deployment-222-8chg8   1/1     Running   0          52m   10.1.30.176 ip-1.eu-central-1.compute.internal   <none>           <none>
simple-deployment-333-wpdwl   1/1     Running   0          52m   10.1.3.27 ip-2.eu-central-1.compute.internal    <none>           <none>

该服务在集群中可以通过集群 DNS 可见,因此 myapp.default.svc.cluster.local 将解析为 172.20.124.66,这是分配给 clusterIP 的 IP 地址。为了将服务暴露到集群外部,我们需要使用不同的服务、Ingress 或 Ingress 控制器,或负载均衡器。我们接下来会讨论这些。

使用 NodePort 服务

NodePort 服务暴露一个静态端口,默认在集群中每个工作节点上使用 30000-32768 之间的端口,然后将流量映射回 80 端口(在接下来的配置中,只定义了一个端口,因此目标端口和服务的端口值相同)映射到匹配选择器的任何 Pod 上。

chapter4-basic-nodeport-service.yaml

---
apiVersion: v1
kind: Service
metadata:
   name: myapp-ext
spec:
   type: NodePort
   ports:
     - protocol: TCP
       port: 80
   selector:
     app: simple-nginx-app

我们创建的服务是一个 NodePort 服务,它选择了具有 app=simple-nginx-app 标签的 Pod,这是另一个 NGINX Pods 的 Deployment。我们可以使用 kubectl get service 命令看到 NodePort 已经成功创建:

$ kubectl get service
NAME  TYPE  CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
myapp-ext NodePort 172.20.225.210 <none>  80:30496/TCP   12m

如果你使用 curl 浏览服务端点,你将看到 NGINX 标准页面(前提是所有工作节点的安全组已经配置为允许流量)。

使用 Ingress

Ingress 在服务之上构建,提供了暴露 HTTP/HTTPS 路由(例如 /login/order)到集群外部的机制。Ingress 独立于底层服务,因此典型的用例是使用单个 Ingress 为多个(微)服务提供一个中央入口点。要使用 Ingress,您需要一个 Ingress 控制器;这个控制器 Kubernetes 并不自带,因此必须安装。我们将使用 NGINX Ingress 控制器。

要安装没有云/AWS 扩展的 NGINX Ingress 控制器,您可以使用以下命令来部署裸金属控制器:

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.2.0/deploy/static/provider/baremetal/deploy.yaml

我们可以通过以下命令验证新的 Ingress 控制器:

$ kubectl get service ingress-nginx-controller --namespace=ingress-nginx
NAME  TYPE   CLUSTER-IP     EXTERNAL-IP    PORT(S)   AGE
ingress-nginx-controller   NodePort   172.20.150.207   <none>        80:31371/TCP,443:31159/TCP   5m27s

我们可以看到 Ingress 控制器作为 NodePort 服务暴露,监听 31371 端口用于 HTTP 连接,监听 31159 端口用于 HTTPS 连接。通常情况下,我们会将负载均衡器放在该 NodePort 服务之前(我们将在下一个示例中探讨这个问题),但暂时我们只使用简单的 NodePort 服务。

我们可以使用之前的服务,并通过以下清单简单地在服务上暴露一个 URL,命令为 $ kubectl create -f chapter4-@ingress.yaml

chapter4-ingress.yaml

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-web
  annotations:
      kubernetes.io/ingress.class: nginx
      nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
   - host: "myweb.packt.com"
     http:
      paths:
      - pathType: Prefix
        path: "/login"
        backend:
          service:
            name: myapp-ext
            port:
              number: 80

我们创建的 Ingress 使用 annotations 来配置我们之前创建的 Ingress 控制器,并在 spec 部分定义路径规则。该规则指出,当请求到达 myweb.packt.com/login 时,需将其发送到 myapp-ext 服务的 80 端口,并将 /login 重写为 /

我们可以通过以下命令进行测试,应该会返回 NGINX 欢迎页面:

curl -H 'Host: myweb.packt.com' http://<WORKERNODEIP>:31371/login

使用 AWS 负载均衡器

由于我们有一个暴露为 NodePort 的 Ingress 控制器和底层服务,我们可以简单地创建一个负载均衡器,并为工作节点和 Ingress 控制器的 NodePort 端口创建目标组。然而,我们希望将 Ingress 控制器与 loadbalancer 集成,以便在 Ingress 控制器扩展和变化时,loadbalancer 配置也会相应变化。

重要提示

确保您已经删除了前一节中的 Ingress($ kubectl delete -f chapter4-ingress.yaml)和 Ingress 控制器($ kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.2.0/deploy/static/provider/baremetal/deploy.yaml)。

我们现在将使用以下命令重新部署与 AWS 负载均衡器集成的 NGINX Ingress 控制器:

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.2.0/deploy/static/provider/aws/deploy.yaml

部署控制器后,我们可以通过以下命令查看 Ingress 控制器。从输出中,我们可以看到将创建一个 AWS 网络负载均衡器NLB)以及为运行在 EKS 中的 Ingress 控制器创建的目标组:

$ kubectl describe service ingress-nginx-controller --namespace=ingress-nginx
Name:                     ingress-nginx-controller
Namespace:                ingress-nginx
……….
Annotations:
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: true
service.beta.kubernetes.io/aws-load-balancer-type: nlb
………
LoadBalancer Ingress: 111-22.elb.eu-central-1.amazonaws.com
Port:                     http  80/TCP
TargetPort:               http/TCP
NodePort:                 http  32163/TCP
Endpoints:                10.1.30.38:80
Port:                     https  443/TCP
TargetPort:               https/TCP
NodePort:                 https  31484/TCP
Endpoints:                10.1.30.38:443
Session Affinity:         None
External Traffic Policy:  Local
HealthCheck NodePort:     31086
…………

我们可以看到通过下面展示的 AWS CLI 命令创建的负载均衡器:

$ aws elbv2 describe-load-balancers
{"LoadBalancers": [{
  IpAddressType": "ipv4",
  "VpcId": "vpc-56567",
 "LoadBalancerArn": "arn:aws:elasticloadbalancing:eu-central-1:11223:loadbalancer/net/111/22",
            "State": {"Code": "active"},
            "DNSName": "111-22.elb.eu-central-1.amazonaws.com",
            "LoadBalancerName": "111-22",
            "CreatedTime": "2022-06-19T07:31:52.901Z",
            "Scheme": "internet-facing",
            "Type": "network",
            "CanonicalHostedZoneId": "Z3F0SRJ5LGBH90",
………
}]}

这是一个公共负载均衡器(internet-facing),因此服务现在可以通过 Ingress 控制器从互联网上访问。你可以使用负载均衡器的 DNSName 来访问链接。现在我们可以重新部署 Ingress 清单(没有任何更改,因为我们刚刚在 Ingress 控制器上方添加了一个 NLB),使用 $ kubectl create -f chapter4-ingress.yaml 命令以通过 NLB 启用对我们服务的访问。

你现在可以通过在任何可以访问互联网的工作站上使用以下命令来测试对服务的访问。该命令将显示 NGINX 欢迎屏幕:

$ curl -H 'Host: myweb.packt.com' http://111-22.elb.eu-central-1.amazonaws.com/login

重要说明

确保你已经移除了 Ingress($ kubectl delete -f chapter4-ingress.yaml)和 Ingress 控制器($ kubectl delete -f raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.2.0/deploy/static/provider/aws/deploy.yaml)。

在这一节中,我们已经了解了不同的方法来部署 Pods,并通过服务、Ingress 和负载均衡器将其暴露。到目前为止,所有示例都是通过命令行操作的。在下一节中,我们将探讨如何使用 AWS 控制台和第三方工具来可视化你的工作负载和应用程序。

可视化你的工作负载

在本书的整个过程中以及在现实中,你主要会通过命令行或 CI/CD 管道与 EKS 进行交互。然而,有时能够以可视化的形式查看你在集群上运行的内容也是很有用的。Kubernetes 提供了一个网页仪表板,但在 EKS 中,你可以通过主要的 EKS 控制台和 CloudWatch(在 第十九章在 EKS 上开发 中讨论)看到大部分集群配置,这样就无需部署一个单独的仪表板了。要访问控制台,登录 aws.amazon.com,并使用具有查看集群权限的凭证登录(请参见 第三章构建你的第一个 EKS 集群)。然后,你可以选择 Amazon 弹性 Kubernetes 服务 | 集群,这时会显示一个你所在区域中运行的集群列表(现在你也可以添加本地集群)。从主视图中,你可以查看集群、其版本,以及是否需要更新(在 第十章升级 EKS 集群 中讨论)。

图 4.1 – 主集群面板

图 4.1 – 主集群面板

你可以通过点击集群名称的超链接来选择集群,这样会带你进入一个更详细的视图,在那里你可以执行以下操作:

  • 通过单击即可升级集群控制平面

  • 删除集群(你可能需要先删除节点组)

  • 查看并修改集群配置

图 4.2 – mycluster 详细面板

图 4.2 – mycluster 详细面板

上面的图显示了 mycluster | 资源 窗口,可以用于获取当前正在运行的 Pods、Deployments 和 Services 列表,但请记住,你在控制台中使用的 IAM 用户/角色必须至少具有集群(RBAC)权限,以读取/获取资源。你还可以在这里创建节点组,并管理配置项,如 公共端点 IP 白名单和附加组件。

通常,最好通过基础设施即代码或通过 CI/CD 流水线进行更改,但你也可以通过控制台管理集群。还有许多其他工具可以从工作站运行,它们在你试图排查问题时非常有用。我经常使用k8slens.dev/,但也有其他选项!

所有这些工具都需要一个网络路由/路径连接到 EKS API 端点(无论是公共的还是私有的),并且需要具有管理 EKS 集群权限的 AWS IAM 凭证(如果你想进行更改,需要拥有 system:masters 权限)。

在你的 .kube/config 文件中,你需要对 users 部分进行更改,加入 AWS_PROFILE 环境变量,以指向具有访问集群权限的 AWS 凭证配置文件。下面是一个示例:

users:
- name: arn:aws:eks:eu-central-1:11222:cluster/mycluster
………………
      env:
      - name: AWS_PROFILE
        value: eksprofile

配置好工作站后,你需要安装并启动 Lens。如果你使用的是临时凭证,那么在 macOS 上从命令行启动 Lens 可能会更方便。我推荐使用 $ open -a lens 命令,启动后,你将拥有一个工作站环境,便于从工作站可视化集群。下一个截图展示了 Lens 提供的集群视图:

图 4.3 – Lens 集群视图

图 4.3 – Lens 集群视图

我非常喜欢 Lens 的一项功能,就是可以添加扩展。例如,如果你安装了 Lauri Nevala 提供的资源地图扩展(github.com/nevalla/lens-resource-map-extension),你可以可视化集群的资源及其相互关联的方式。要查看完整的扩展列表,请访问 github.com/lensapp/lens-extensions/blob/main/README.md

以下截图显示了资源地图的示例:

图 4.4 – 示例 资源 地图

图 4.4 – 示例资源地图

现在,你已经熟悉了如何使用 AWS 控制台和 Lens 等第三方工具来可视化集群中的工作负载。

总的来说,我们已经查看了如何验证与集群的连接,如何部署并可视化集群中的 Pods。现在,让我们回顾一下本章的关键学习点。

总结

在本章中,我们检查了不同的应用程序部署方式,从创建一个简单的 Pod 开始,并在此基础上构建部署、服务、Ingress,最后部署 AWS NLB 和 NGINX Ingress 控制器,将服务暴露到互联网。我们讨论了如何通过 Deployment 和 Service 提供更强的弹性和抽象,以及如何使用服务、Ingress 和负载均衡器以安全/弹性的方式将服务暴露到集群/VPC 外部。

在本章中,我们使用 Kubernetes YAML 清单来说明如何通过 kubectl 构建和部署这些对象。现在,你可以使用基本的 YAML 清单和 kubectl 在 EKS 中部署应用程序。在下一章中,我们将探讨如何使用 Helm 来创建灵活的清单,这些清单可以在部署时进行参数化,以支持不同的需求和/或环境。

进一步阅读

  • 理解 Deployments:

kubernetes.io/docs/concepts/workloads/controllers/deployment/

  • 理解服务:

kubernetes.io/docs/concepts/services-networking/service/

  • 使用 NGINX Ingress 控制器和 AWS NLB:

aws.amazon.com/blogs/opensource/network-load-balancer-nginx-ingress-controller-eks/

  • NGINX Ingress 控制器:

kubernetes.github.io/ingress-nginx/examples/

  • Kubernetes Lens:

k8slens.dev/

  • 部署和使用 Kubernetes Dashboard:

kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/

第五章:使用 Helm 管理 Kubernetes 应用程序

在上一章中,我们描述了如何使用 kubectl 和标准 Kubernetes 清单部署简单应用程序。使用这种方法的挑战在于,清单文件是固定的。如果您想在不同环境(开发、测试、生产等)中更改 Web 服务器的标签,您需要有多个清单文件,或者每次部署时都需要修改清单内容。

在本章中,我们将介绍 Helm,这是一款可以用于定义、安装和升级复杂应用程序的工具,能够轻松地为不同环境自定义部署。具体来说,我们将覆盖以下主题:

  • 理解 Helm 及其架构

  • 安装 Helm 二进制文件

  • 使用 Helm 部署示例 Kubernetes 应用程序

  • 创建、部署、更新和回滚 Helm 图表

  • 通过 Helm 删除应用程序

  • 使用 Lens 部署 Helm 图表

重要说明

Helm 是 Kubernetes 上的一个抽象工具,因此在 EKS 或其他 Kubernetes 发行版或部署中使用 Helm 没有区别。本章将重点介绍 Helm 的基本功能。更多高级操作/配置可以在 进一步 阅读 部分找到链接。

技术要求

在开始本章之前,请确保以下事项已完成:

  • 您有一个正常工作的 EKS 集群,并能够执行管理任务

  • 您的工作站上已安装并正确配置了 kubectl

  • 您已连接到 EKS API 端点

  • 您熟悉 YAML、基础网络和 EKS 架构

理解 Helm 及其架构

正如我们在前几章中看到的,Kubernetes 的 YAML 模板适用于简单的应用程序。然而,当您面对多组件的复杂应用程序时,这些组件之间有多个依赖关系,并且需要频繁地使用如蓝绿部署等技术来部署和更新这些组件时,您需要更多的东西;您需要一个包管理工具。

包管理不是一个新概念;您可以在 Linux 上的 APT/YUM、Mac 上的 Homebrew 或 Windows 上的 Chocolatey 等软件中看到类似的概念和关键的软件包管理工具。同样,Helm 可以被视为 Kubernetes 的包管理工具。

Helm 由以下组件组成:

  • 图表:一包预配置的 Kubernetes 资源

  • 发布:使用 Helm 部署到集群中的图表的特定实例

  • 仓库:一组已发布的图表,可以提供给其他人使用

  • Helm 二进制文件:用于部署图表/发布的工具

在接下来的几节中,我们将更详细地探讨这些组件。

Helm 的优点

使用 Helm 的主要好处是简化 Kubernetes 资源的创建和部署方式。Helm 允许开发者采用 不要重复自己DRY)的方法,允许设置默认属性,但仍然允许根据不同的用例或环境进行修改(覆盖)。

开发者还可以通过 Helm Chart 仓库 轻松分享模板。例如,如果你想在开发环境中安装 Prometheus(一个在 Kubernetes 上的开源监控系统),你可以手动创建清单,指定标准镜像,设置任何支持资源——如 ingress、deployment 和 service——配置任何环境变量,并为生产环境重复此过程。

你可以通过以下命令简单地添加 Prometheus 的 Helm Chart 仓库,然后进行部署,避免了所有这些复杂的工作:

$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm install stable/prometheus

Helm 也有一些替代方案,譬如 Kustomize(kustomize.io/),它在 YAML 层上构建,并为你的应用程序创建一个自定义操作符来管理其生命周期;例如,Kafka 操作符。然而,Helm 仍然是定制和部署应用程序的最简单方法。

了解 Helm charts

Helm 的基本配置项是 chart。一个包含 Kubernetes 应用程序的模板,可以通过 $ helm create <mychartname> 命令创建该结构:

mychartname/
|
|- .helmignore
|
|- Chart.yaml
|
|- values.yaml
|
|- charts/
|
|- templates/

让我们来看一下 chart 的每个组件:

  • .helmignore 文件类似于 .gitignore 文件,用于指定 Helm 命令应忽略的文件或目录。

  • Chart.yaml 文件包含有关你正在打包的 chart 的元数据,例如你自己的 chart 版本。

  • values.yaml 文件存储用于部署的任何值。你通常会看到一个文件,默认值,但也可以使用多个值文件来适应不同的环境。

  • charts 目录用于存储你的 chart 可能依赖的其他 charts。

  • templates 目录包含你为支持应用程序部署所创建的实际清单。它可能包含多个 YAML 文件,用于部署 pod、配置映射、秘密等。

值通过 {{ define }} 指令转入 chart 中,这意味着模板比标准的 Kubernetes 清单更灵活。以下是一个 Helm 清单的示例,展示了资源(在这个例子中是 ConfigMap)如何被修改:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"

在这个例子中,ConfigMap 的名称将是 Release.Name-configmap 字符串的组合。例如,如果你的 chart 名称是 CM1,那么生成的 ConfigMap 将被命名为 CM1-configmap

现在我们已经讨论了基本配置,让我们来看看如何实际安装和使用 Helm。

安装 Helm 二进制文件

Helm 可以轻松安装,并且能够与许多不同的操作系统兼容。你可以参考以下说明在系统中设置 Helm:

helm.sh/docs/intro/install/

您必须配置 kubectl 以便与 Amazon EKS 一起使用。如果您还没有这样做,请参考第三章构建您的第一个 EKS 集群,以帮助您正确配置 kubectl。

在 Linux 上,您可以使用以下命令下载并安装 Helm 二进制文件:

$ curl -L https://git.io/get_helm.sh | bash -s -- --version v3.8.2

在编写本书时,Helm 3.9.x 存在使用 AWS 身份验证插件的一些问题,因此使用 v3.8.2 版本。您需要确保/usr/local/bin/目录在您的PATH中。

使用 Helm 部署示例 Kubernetes 应用程序

在上一章中,我们使用以下命令部署了 NGINX 入口控制器:

$ kubectl create -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/I

如果我们想使用 Helm 安装它,需要执行以下任务:

  1. 将公共稳定仓库添加到您的 Helm 配置中:

    $ helm repo add nginx-stable https://helm.nginx.com/stable
    
  2. 刷新仓库信息:

    $ helm repo update
    
  3. 然后我们可以显示仓库中的图表:

    $ helm search repo nginx-stable
    
  4. 其中一个图表是nginx-ingress,可以使用以下命令进行安装:

    $ helm install my-release nginx-stable/nginx-ingress
    
  5. 图表发布可以使用以下任意命令查看:

    $ helm list
    $ helm history my-release
    
  6. 您还可以使用以下命令查看 Kubernetes 资源:

    $ kubectl api-resources --verbs=list -o name | xargs -n 1 kubectl get --show-kind -l app.kubernetes.io/instance=my-release --ignore-not-found -o name
    

让我们看看如何从头开始创建一个新的 Helm 图表。

创建、部署、更新和回滚 Helm 图表

如您从前面的示例中看到的,使用 Helm 部署预打包的应用程序非常简单。部署您自己的应用程序也很容易。我们从以下命令开始,这将会在您当前的目录下创建一个名为myhelmchart的目录,并填充相关文件和模板:

$ helm create myhelmchart

默认情况下,由此命令创建的values.yaml文件包含对单个 NGINX Pod 的引用,并创建一个仅在集群内可访问的ClusterIP服务。默认文件中的关键值如下所示:

replicaCount: 1
image:
  repository: nginx
service:
  type: ClusterIP

我们可以使用以下命令轻松部署这个新的 Helm 图表:

$ helm install example ./myhelmchart --set service.type=NodePort

这将覆盖values.yaml文件中看到的service.type值,并设置为NodePort服务,因此它现在会暴露在集群外部。然后我们可以使用以下命令验证图表部署,并查看创建的NodePort服务的名称以及其他 Kubernetes 资源:

$ kubectl api-resources --verbs=list -o name | xargs -n 1 kubectl get --show-kind -l app.kubernetes.io/instance=example --ignore-not-found -o name
endpoints/example-myhelmchart
pod/example-myhelmchart-cb76665d4-sq4lk
serviceaccount/example-myhelmchart
service/example-myhelmchart
deployment.apps/example-myhelmchart
replicaset.apps/example-myhelmchart-cb76665d4
endpointslice.discovery.k8s.io/example-myhelmchart-8kw8t

如果您使用前一步中的服务名称运行以下命令,您将能够提取服务的 IP 地址和端口,并通过 curl 访问NodePort服务:

$ export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services example-myhelmchart) | curl http://$NODE_IP:$NODE_PORT

要更新 Helm 部署,我们将执行以下步骤:

  1. 修改values.yaml文件,并将replicaCount:增加到2。我们还可以将service.type的值更改为NodePort

  2. 修改Chart.yaml文件,并将version更新为0.2.0

  3. 验证更改:

    $ helm lint
    
  4. 然后推出更改:

    $ helm upgrade example ./myhelmchart
    
  5. 然后我们可以验证部署:

    $ helm history example
    

当您运行前面的命令时,您会注意到下一个显示的修订版本号:

$ helm history example
REVISION  UPDATED STATUS   CHART   APP VERSION     DESCRIPTION
1 Sat xx superseded  myhelmchart-0.1.0 1.16.0 Install complete
2 Sat xx deployed  myhelmchart-0.2.0  1.16.0 Upgrade complete
  1. 我们还可以验证是否存在两个 Pod 用于该部署:

    kubectl get pod | grep example
    

你可以使用helm rollback example 1命令轻松回滚到先前的版本,其中1表示你想要回滚的版本。这是 Helm 相较于基础 Kubernetes 清单的主要优势之一,每次对清单的更改都可以被版本化并作为新的版本进行部署,如果发生问题,你可以轻松回滚到先前的版本/修订。

helm list命令可以显示集群中所有的版本,这些版本作为 Kubernetes Secrets 存储在发布部署的命名空间中。接下来,我们来看看如何移除我们的 Helm 应用程序。

通过 Helm 命令删除应用程序

Helm 提供了一个简单的uninstall命令来删除应用程序发布。第一步是使用helm list命令确定要删除的 Helm 部署:

$ helm list --all-namespaces
NAME  NAMESPACE REVISION  UPDATED     STATUS          CHART                    APP VERSION
cm1             default             1          2022- deployed        myhelmchart-0.1.0       1.16.0
example         default         4           2022- deployed        myhelmchart-0.2.0       1.16.0
my-release      default        1          2022- deployed        nginx-ingress-0.13.2    2.2.2

在这个示例中,我们要删除示例部署,因此我们可以简单地运行$ helm uninstall example命令,所有由 Chart 或 Charts 创建的资源将被删除。

重要提示

这也会删除所有的部署历史。请确保你已经使用$ helm uninstall命令删除了所有 Helm Chart。

在这一节中,我们回顾了使用 Helm 二进制文件来部署公共 Chart,以及创建、更新、回滚和删除自己的 Chart。在下一节中,我们将展示如何使用 Lens 来部署 Chart。

使用 Lens 部署 Helm Chart

在上一章中,我们讨论了如何使用 Lens 可视化 Kubernetes 资源。然而,你也可以使用 Lens 来管理 Helm Chart。请参考第四章在 EKS 上运行你的第一个应用程序可视化你的工作负载部分),获取关于 Lens 设置的指导。

默认情况下,Lens 会从公共 Artifact Hub(artifacthub.io/)和 Bitnami 拉取可用的 Helm 仓库。由于我们将重新部署通过 Helm 命令行接口CLI)部署的 NGINX Ingress Controller,我们需要添加一个自定义仓库。为此,请参照图 5.1,按照以下步骤操作:

  1. 从主工具栏中选择Lens | 首选项

  2. 然后选择Kubernetes

  3. 从 Kubernetes 面板中选择添加自定义 Helm 仓库

  4. 这将显示一个弹出框,工作方式与helm repo add命令相同。在这里,我们将添加之前示例中使用的 NGINX 仓库:helm.nginx.com/stable

图 5.1 – 在 Lens 中添加自定义 Helm 仓库

图 5.1 – 在 Lens 中添加自定义 Helm 仓库

一旦仓库被添加,我们现在可以从中部署 Chart。为此,请按照以下步骤操作,并参照图 5.2

  1. 选择你的集群。

  2. 选择Helm | Charts。这将显示基于你所添加仓库的所有可用 Chart。

  3. 如下图所示,过滤掉 NGINX,你将看到两个 NGINX Ingress 控制器图表:一个来自 Bitnami,另一个来自 NGINX。

图 5.2 – 在 Lens 中找到所需的图表

图 5.2 – 在 Lens 中找到所需的图表

  1. 点击 NGINX 图表。

  2. 这将弹出另一个面板,在那里你必须点击 安装。填写详情(或保持不变)并再次点击 安装。图表将被部署,如下图所示:

图 5.3 – 在 Lens 中安装图表

图 5.3 – 在 Lens 中安装图表

  1. 现在你可以点击 发布 标签,看到所有已部署的 Helm 图表。在以下示例中,我们只部署了 NGINX Ingress 控制器。从 发布 标签中,你还可以通过点击图表右侧的三个点(即 kebab 菜单)来升级或删除发布。

图 5.4 – 在 Lens 中查看发布

图 5.4 – 在 Lens 中查看发布

如你所见,Lens 执行与 Helm 二进制文件相同的功能,只是以图形化视图呈现。正如前一章所讨论的,Lens 还允许你查看正在运行的工作负载,而这在过去我们需要使用 Kubectl 来完成。因此,Lens 帮助将这些工具整合在一起,但如果你要在生产环境中使用多个集群,仍然推荐你了解如何使用 Kubectl 和 Helm。

总结

在本章中,我们学习了如何使用 Helm 加速 Kubernetes 上的应用部署,并提高模板创建的效率。这包括如何安装 Helm CLI,并使用它部署托管在公共仓库中的应用(图表)。接着我们创建了自己的图表,进行了自定义,部署,修改配置,部署新版本,回滚到以前的版本,最后从集群中删除它。

接着我们讨论了如何使用第三方工具 Lens 执行类似的操作,以及它如何实现相同的功能,同时提供图形用户界面,并集成了多个视图,如 Kubernetes 资源(kubectl)和 Helm 发布。

在下一章中,我们将更深入地了解如何访问和保护 EKS 集群,重点讲解如何访问 EKS 集群端点并进行用户身份验证。

深入阅读

第二部分:深入 EKS

在这一部分,我们将深入探讨 EKS 及其各种组件。本部分旨在揭开安全性、网络和节点组等关键方面的神秘面纱,帮助你全面理解每个功能。最后,你将学到如何升级 EKS 集群版本,并跟随上游 Kubernetes 发布节奏的策略。

本部分包含以下主题:

  • 第六章在 EKS 上保护和访问集群

  • 第七章EKS 中的网络配置

  • 第八章在 EKS 上管理工作节点

  • 第九章EKS 中的高级网络配置

  • 第十章升级 EKS 集群

第六章:在 EKS 上保护和访问集群

本章将介绍 Kubernetes 中认证和授权的一般概念,还将讨论这些概念与弹性 Kubernetes 服务EKS)之间的差异。我们还将了解如何配置 EKS 和客户端工具以安全地进行通信。

认证验证用户或服务的身份,授权则管理他们可以执行的操作。在本章中,我们将回顾 EKS 中实现认证和授权的机制,以便你可以利用这些机制构建安全的集群。具体而言,我们将讨论以下主题:

  • 理解关键的 Kubernetes 概念

  • 配置 EKS 集群访问

理解关键的 Kubernetes 概念

Kubernetes 集群有两类用户:由 Kubernetes 管理的服务账户和普通用户(如管理员、开发人员等)。KubernetesK8s)具有可扩展性,支持多个认证插件。我们将重点讨论最常见的客户端证书,同时讨论 Kubernetes 中的通用用户认证/授权。

使用客户端证书插件时,用户在提供由集群证书 授权机构CA)签名的有效证书时会被视为已认证。

拥有有效证书后,Kubernetes 从/CN=bob中的常用名称字段确定用户名,而组信息则提供在/O=dev中。从此时起,基于角色的访问控制RBAC)子系统将决定用户是否有权限在资源上执行特定操作。

以下图示说明了这一概念。请注意,服务账户将在第十三章中讨论,使用 IAM 授予应用程序访问权限

图 6.1 – 标准 Kubernetes RBAC 子系统

图 6.1 – 标准 Kubernetes RBAC 子系统

为了为普通用户生成证书,你需要使用操作系统工具(如 OpenSSL)生成 PKI 私钥和 CSR(见下例),然后将 CSR 请求以 base64 编码导出,由集群 CA 进行签名:

$ openssl genrsa -out myuser.key 2048
$ openssl req -new -key myuser.key -out myuser.csr
$ cat myuser.csr | base64 | tr -d "\n"

生成的编码字符串可以通过CertificateSigningRequest类型提交给 K8s 集群进行签名。以下是一个示例:

apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: myuser
spec:
  request: <BASE64.csr>
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # one day
  usages:
  - client auth

现在我们已经了解了标准 K8s 认证的工作原理,接下来让我们看看它在 EKS 中的默认工作方式。

理解默认的 EKS 认证方法

管理一个或多个 K8s 集群的用户、组和证书可能是一个运营挑战。幸运的是,EKS 默认将此任务转交给 AWS 身份和访问管理IAM)服务。IAM 是一个全球分布的服务,允许你创建用户和组,并分配包括 AWS EKS API 在内的 AWS 权限。

默认情况下,当你创建一个 EKS 集群时,创建该集群的 IAM 实体会自动被授予 system:masters 权限,实际上使其成为 EKS 集群的系统管理员角色。这意味着没有额外配置的情况下,用于集群创建的 IAM 身份是唯一能够对 EKS 集群执行任何操作的身份。

这是开始使用 EKS 时的典型错误,因为可能使用 CI/CD 管道来创建集群,但其 IAM 身份与常规用户不同。因此,当他们与集群交互时,他们没有 K8s 权限。EKS 集群已预先集成 AWS IAM。下图说明了用户和 EKS 集群如何与 IAM 服务交互。

图 6.2 – EKS 身份验证流程

图 6.2 – EKS 身份验证流程

我们将详细讨论 图 6.2 中显示的 EKS 身份验证流程的每个步骤:

  1. 起始点是客户端检索集群配置,更新 update-kubeconfig CLI 命令是最简单的方法,它会使用 DescribeCluster API 操作。任何使用此 API 调用的用户必须具备一个 IAM 身份,并且有权限使用该 API 自动更新配置文件。下面是名为 mycluster 的集群示例:
$ aws eks update-kubeconfig --name mycluster
  1. 现在需要生成并在每个 EKS API 中使用一个持有者令牌(get-token CLI 命令或使用 AWS IAM 身份验证器自动生成):

    $ aws eks get-token --cluster-name mycluster
    
  2. 持有者令牌要么手动添加到请求中(例如,在 kubectl 命令行中),要么由 IAM 身份验证器/kubectl 命令自动添加。

  3. 这个持有者令牌现在通过一个令牌——authentication-webhook——在 AWS IAM 服务中进行验证,该令牌由 EKS 中的 IAM 身份验证器服务使用。如果验证通过,则请求会传递到 K8s RBAC 子系统。

  4. 通过与经过身份验证的 IAM 身份关联的 IAM 策略授予的权限集对 EKS 集群权限没有影响。IAM 和 RBAC 子系统之间的桥梁是 aws-auth ConfigMap,它提供 IAM 主体(角色/用户/组)与 Kubernetes 实体(用户/组)之间的映射。

  5. 操作(在授权的情况下)会返回给客户端,或者他们会收到“未授权”响应。

到目前为止,我们已经讨论了用户身份是如何通过身份验证的。下一部分将描述经过身份验证的用户的请求是如何被授权的。

配置 aws-auth ConfigMap 以进行授权

我们将在本节稍后介绍如何配置和维护 aws-auth ConfigMap,但需要理解的是,这个 ConfigMap 管理着 AWS IAM 身份与 Kubernetes 身份和权限之间的关系。如果在 aws-auth ConfigMap 中没有相应的条目,IAM 用户或用户组将无法执行任何 K8s 操作,无论 IAM 角色或策略中分配了什么权限。

EKS 使用 OpenID Connect (OIDC) 身份提供者作为一种方法来验证/授权用户访问你的集群。这意味着每个集群都会被赋予一个唯一的 OIDC 身份,可以作为 AWS IAM 策略中的受信实体使用。你可以通过以下命令验证集群的身份:

$ aws eks describe-cluster --name my-cluster --query "cluster.identity.oidc.issuer" --output text

OIDC 身份提供者可以与 AWS IAM 一起使用,也可以与其他符合 OIDC 标准的身份提供者一起使用。这意味着你可以在像 GitHub Enterprise 或 GitLab 这样的平台中管理用户/密码和权限,而不是使用 AWS IAM,并通过 OIDC 提供者来验证/授权用户。你可以将一个 OIDC 身份提供者关联到你的集群,因此不能同时使用 IAM 和另一个身份提供者。

访问集群端点

到目前为止,我们已经讨论了如何验证和授权在 EKS 集群上的操作;然而,要做到这一点,你需要能够通过 HTTPS 与集群的端点进行通信。一旦你更新了 certificate-authority-data,如何访问该端点将取决于你如何配置 EKS 端点,无论是仅公开、仅私有,还是两者都支持。

如前所述,EKS 是一个托管服务,因此控制平面(API/etcd)服务器运行在 AWS 管理的 VPC 中。工作节点(通常是 EC2)运行在客户的 VPC 中,并使用通过 DNS 查找服务器返回的任何 IP 地址与控制平面进行通信。以下图示说明了用户如何访问公共或私有 EKS 端点:

图 6.3 – EKS 端点访问

图 6.3 – EKS 端点访问

如前图所示,访问 EKS 端点所需的步骤如下:

  1. 初步要求是获取 EKS 集群的 DNS 名称。在大多数用户访问场景中,这个步骤已经通过 update-kubeconfig CLI 命令完成,但当工作节点被创建时,它们会调用 AWS EKS API 获取集群配置和 DNS 信息。写作时,AWS EKS API 仅是一个公共 API(没有私有端点),因此所有工作节点需要通过 NAT、互联网和/或传输网关访问公共 API。

  2. 对于公共端点,DNS 名称解析为 AWS 管理的 VPC 中托管的公共 网络负载均衡器 (NLB)。这意味着所有用户和工作节点的通信都会经过这个公共端点(尽管工作节点流量不会离开 AWS 主干网)。

  3. 对于私有端点,DNS 名称解析到附加到客户 VPC 的私有端点(这使用由 AWS 管理的私有托管 Route 53 区域)。更具体地说,它解析到 VPC 私有 IP 地址,这意味着它只能在 VPC 内部或通过使用传输网关、VPN 和/或 Direct Connect 的私有连接进行访问。在这种情况下,客户的 VPC 和 AWS 管理的 VPC 之间的私有连接是双向的,由控制平面和用户/工作节点共同使用。

可以启用公共和私有端点,在这种情况下,用户可以通过公共 NLB 访问 K8s API,也可以通过私有端点访问。在此模型中,所有 API 服务器/工作节点通信都通过私有端点进行。

配置 EKS 集群访问

在本节中,我们将更详细地查看访问集群所需的两个关键文件的配置:kubeconfigaws-auth,以及如何使用 IP 控制来保护 EKS 端点。

配置.kube/config

clusters列表中包含每个要访问的集群条目,包含 DNS 名称和 TLS 证书以允许通信。这些条目可以手动添加或通过update-kubeconfig CLI 命令添加。以下是一个示例kubeconfig文件:

clusters:
- cluster:
  certificate-authority-data: xx==
  server: https://65787.gr7.eu-central-1.eks.amazonaws.com
  name: arn:aws:eks:eu-central-1:111999:cluster/mycluster

kubeconfig文件中的context部分用于将访问参数分组到一个客户端工具中,例如 kubectl。你可以为访问单个集群创建不同的上下文。在以下示例中,我们将myuser用户和mycluster集群组合到mycontext上下文中:

contexts:
- context:
    cluster: arn:aws:eks:eu-central-1:111999:cluster/mycluster
    user: arn:aws:eks:eu-central-1:111999:cluster/myuser
    name: arn:aws:eks:eu-central-1:111999:cluster/mycontext

最后,kubeconfig文件中的users部分保存与上下文相关的用户特定参数。在以下示例中,用户使用aws eks get-token命令基于为mycluster集群调用者配置的 IAM 身份自动提供承载令牌,该集群位于eu-central-1区域,账户 ID 为111999

users:
- name: arn:aws:eks:eu-central-1:111999:cluster/myuser
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      args:
      - --region
      - eu-central-1
      - eks
      - get-token
      - --cluster-name
      - mycluster
      command: aws

配置你的kubeconfig文件是与 EKS 集群建立通信的关键部分,以下命令可用于验证/查看配置:

$ kubectl config view
$ kubectl config view -o template --template='{{ index . "current-context" }}'

到目前为止,我们已经回顾了 K8s 客户端(如 kubectl)如何向 API 服务器提供用户身份。下一部分将回顾如何使用aws-auth配置映射授权该用户身份。

配置 aws-auth 配置映射

正确配置的客户端kubeconfig文件提供承载令牌,使 EKS 能够识别和验证用户。一旦验证了身份,RBAC 子系统和aws-auth配置映射将验证用户是否具有正确的权限。我们通过在配置映射的data部分中的角色详细信息来添加用户权限。每个条目支持以下参数:

  • groups:角色映射到的 Kubernetes 组列表。

  • rolearn:与用户持有的令牌关联的 IAM 角色的 ARN。

  • username:Kubernetes 中的用户名,用于映射到 IAM 角色。如前所述,这个字段并没有被使用,因此无需映射到现有的用户。

在以下示例中,账户 111999 中的 myIAMRole IAM 角色被映射到 system:masters 组,并分配了 K8s 用户名 creator-account。这是你在 aws-auth ConfigMap 中将看到的默认配置示例:

- groups:
  - system:masters
  rolearn: arn:aws:iam::111999:role/myIAMrole
  username: creatorAccount

你可以使用 kubectl 或像 eksctl 这样的 IAC 工具直接修改 ConfigMap,如下所示:

$ kubectl edit cm aws-auth -n kube-system
eksctl create iamidentitymapping --cluster  mycluster --region=eu-central-1 --arn arn:aws:iam::111999:role/myIAMrole --group system:masters --username creatorAccount

不建议使用这种方法,因为没有版本/变更控制。更好的方法是将 ConfigMap 清单放入 Git 仓库,然后使用 CI/CD 管道来推动/拉取变更。你仍然可以使用 kubectleksctl 来进行更改,但这是通过版本控制下的文件完成的,并通过经过审计的管道进行管理,该管道可以进行任意数量的构建检查或测试。通常,部署 EKS 集群和增量管理 ConfigMap 时将遵循以下步骤:

  1. 使用你首选的基础设施即代码工具和/或管道部署 EKS 集群。

  2. 将默认的 aws-auth ConfigMap 导出到一个独立的版本控制仓库/清单,并拥有自己的部署管道。可以通过运行以下命令完成此操作:

    kubectl get cm aws-auth -n kube-system -o yaml > xx.yaml
    
  3. 利用合适的代码审查流程,添加新组/用户和角色映射。请注意,配置中引用的 IAM 角色必须存在,因此它必须首先在 AWS IAM 服务中创建。

  4. 使用以下命令,通过推送(CI/CD)或拉取(GitOps)方法更新你的集群:

    kubectl patch configmap/aws-auth -n kube-system –patch "$(cat /tmp/xx.yml)"
    
  5. 步骤 3 和步骤 4 需要在你不断添加和删除用户/组到 aws-auth 文件时反复执行。

到目前为止,我们已经讨论了当请求到达 K8s API 服务器端点时,用户如何进行身份验证/授权。在下一节中,我们将讨论如何保护 API 端点的访问。

保护 EKS 端点

总体来说,最好使用私有 EKS 端点。尽管访问任何 EKS 端点需要有效的 TLS 证书(如果使用客户端证书)和适当的 IAM/RBAC 权限,但如果这些被泄露且你有一个公共端点,那么你的集群将可以从世界任何地方访问。如果需要使用公共端点,请确保配置公共访问 CIDR 范围,以限制哪些公共 IP 地址可以访问。在以下示例中,访问 mycluster 公共集群已被限制为单个 /32 IP 地址:

$ aws eks describe-cluster --name mycluster
{    "cluster": {
…….
            "endpointPublicAccess": true,
            "publicAccessCidrs": ["203.0.113.5/32 "]}}

EKS API 网络接口由一个单独的安全组保护,提供有状态的 IP 入站保护。因此,尽管 EKS 私有端点只能从 VPC 或连接的私有网络访问,但与 API 端点关联的安全组(在以下示例中显示为securityGroupIds)可以作为附加控制来限制对特定 IP 地址的访问:

$ aws eks describe-cluster --name mycluster
{"cluster": {
………..
            "securityGroupIds": ["sg-5656576d"],
            "clusterSecurityGroupId": "sg-5657657s"}}

请注意,API 安全组与clusterSecurityGroupId安全组不同,后者用于保护工作节点。

到此为止,你已经理解或至少对 EKS 如何进行认证和授权、以及如何保护 API 端点有了一定的了解。接下来,我们将回顾本章的关键学习点。

总结

在本章中,我们探讨了原生 Kubernetes 中的认证和授权的基本概念,以及 EKS 的不同之处。我们描述了 EKS 如何默认与 AWS IAM 集成,并且为了使 EKS 认证用户,需要通过像kubectl这样的客户端工具生成一个承载令牌。这个承载令牌可以通过手动使用get-token命令行操作生成,或通过kubeconfig文件自动生成,并将在每个 API 请求中提交,并由 EKS 自动验证。

我们还描述了aws-auth ConfigMap 如何被 Kubernetes RBAC 子系统用来接受或拒绝任何 API 请求。重要的是,将此文件放在版本控制下,并使用 CI/CD 管道管理更改,因为默认情况下,只有集群创建者才有权限对集群进行任何操作。

最后,我们讨论了如何通过 IP 白名单和/或安全组保护 API 端点的访问,以及通常情况下,使用私有集群更为安全。

在下一章,我们将讨论 Kubernetes 网络及如何配置 EKS 使用AWS 虚拟私有****云VPC)。

深入阅读

  • AWS IAM 认证器概述:

docs.aws.amazon.com/eks/latest/userguide/install-aws-iam-authenticator.html

  • K8s 中 Webhook 认证概述:

kubernetes.io/docs/reference/access-authn-autWhz/authentication/#webhook-token-authentication

  • AWS OIDC 提供商集成概述:

github.com/awsdocs/amazon-eks-user-guide/blob/master/doc_source/authenticate-oidc-identity-provider.md

第七章:EKS 中的网络

KubernetesK8s)对外部网络配置没有严格规定。这意味着在 Kubernetes 中可以使用多个网络插件和配置,以满足安全性、延迟和操作要求。

在本章中,我们将重点讨论标准 K8s Pod 和集群网络的工作原理,然后讨论 AWS 虚拟私有云VPC)中的相似性和差异性。具体来说,我们将覆盖以下内容:

  • 理解 Kubernetes 中的网络

  • 掌握基本的 AWS 网络知识

  • 理解 EKS 网络

  • 使用 VPC CNI 配置 EKS 网络

  • 常见的网络问题

读者应该熟悉 TCP/IP 网络、AWS 中的网络工作原理以及 NAT 的概念。本章旨在使读者具备配置和管理 EKS 网络(适用于一个或多个集群)的技能。

理解 Kubernetes 中的网络

Kubernetes 被设计为可扩展的,因此它支持多种网络实现,所有这些实现都符合明确定义的网络模型。K8s 有一些基本的网络规则,所有网络插件必须遵循:

  • 每个 Pod 都有自己的 IP 地址

  • Pod 中的容器共享 Pod 的 IP 地址

  • Pods 可以通过 Pod IP 地址(无需 NAT)与集群中所有其他 Pods 通信

  • 网络策略用于在网络层面上对 Pods 进行隔离

出于合规性原因,任何 K8s 网络实现必须支持 容器网络接口CNI)规范,这是 云原生计算基金会CNCF)的一个项目。CNI 规范包括用于编写插件以配置容器中网络接口的指南和库。虽然在单一集群中可以拥有多个 CNI,但默认情况下,一个 K8s 集群只会配置为支持单个 CNI。有许多类型和提供商的 CNI 插件,但它们都允许 Pods 连接到外部网络和/或分配 Pod IP 地址。

在深入讨论 EKS 网络之前,了解 K8s 中网络的一般工作原理是非常重要的,因为大多数 CNI 实现都遵循这一模式。

Kubernetes 中的网络实现

Pod 是 Kubernetes 中可以部署和管理的最小单位。一个 Pod 可以包含多个容器。Pod 中的容器共享网络命名空间,这意味着它们共享相同的 IP 地址、网络端口空间和以太网接口。下图展示了同一节点内以及同一集群中跨节点的 Pod 间连接。

图 7.1 – 基本 Pod 网络

图 7.1 – 基本 Pod 网络

K8s 网络通信有多种方式,取决于源和目标:

  • 由于 Pod 中的容器共享相同的网络命名空间和端口空间,它们可以使用本地主机(127.0.0.1)地址进行相互通信。

  • 每个 Pod 在主机的根网络命名空间中都有一个对应的接口(veth),并且在其自己的网络命名空间中也有一个接口。这被称为 veth 对,它充当 Pod 网络命名空间和主机网络(具有实际以太网接口)之间的虚拟网络电缆。希望互相通信的 Pod 使用集群 DNS 解析服务名称到 IP 地址,并使用 ARP 协议将 IP 地址映射到 Pod 以太网地址。

  • 如果 Pod 位于另一个节点,集群 DNS 将解析 IP 地址。在 ARP 请求失败的情况下,数据包将被路由出主机到网络中,希望它能找到通向目标 IP 地址的路由。

CNI 与 kubelet 集成,kubelet 是在所有工作节点上运行的主要 K8s 代理。当创建一个新的 Pod 时,它没有网络接口。kubelet 会向 CNI 发送一个 ADD 命令,然后 CNI 负责以下操作:

  1. 将网络接口插入到容器网络命名空间中(eth0

  2. 对主机进行必要的更改,如创建 veth 接口并将其附加到 Bridge0eth0 接口上

  3. 为接口分配 IP 地址并设置相关的路由

Kubernetes 在基本 Pod 网络之上添加了进一步的抽象。一个 Kubernetes 集群允许多个相同的 Pod 副本在多个主机上部署,并允许入口流量路由到这些主机中的任何一台。服务有不同类型;在这个示例中,我们将重点关注 NodePort 服务。当创建一个服务时,它通常会基于标签选择 Pods。它会创建一个新的 DNS 名称、虚拟 IP 地址,为每个节点分配一个动态端口,并保持一个节点与其托管的 Pods 之间的映射,映射依据服务规格中定义的标签。这在下面的图中有所显示。

图 7.2 – Nodeport 服务

图 7.2 – Nodeport 服务

当流量到达服务时(使用服务的 DNS 名称或主机:动态端口组合),iptables 或 IP 虚拟服务器(IPVS)被用来将请求服务地址重写为相关的 Pod 地址(由 kube-proxy 控制),然后应用前述的基本 Pod 网络规则。在服务 1(图 7.2)的情况下,流量可以发送到每个节点,并且目标地址将被重写为运行在该节点上的 Pod。在服务 2 的情况下,到达节点 3 的流量没有本地 Pod,因此流量将被发送到节点 1 或节点 2。

默认情况下,流量将从节点 3 进行源地址转换(SNAT),因此无论 Pods 位于何处,流量总是流入并流出节点 3。Kubernetes 网络代理(kube-proxy)在每个节点上运行,负责管理服务、请求(包括 SNAT)以及 Pod 的负载均衡。

SNAT 表示将 IP 数据包的源 IP 地址替换为另一个地址。在大多数情况下,这将是节点以太网地址的 IP 地址。目标 NATDNAT)是指将目标 IP 地址替换为另一个地址,通常是 Pod 的 IP 地址。下图展示了这些概念。

图 7.3 – K8s 源/目标 NAT

图 7.3 – K8s 源/目标 NAT

图 7.3中,以 nodePort 服务为例:

  1. 客户端(10.2.3.4)发送的流量在节点 3 上接收,目标是通过 nodeport 服务端口 3124 暴露的服务。

  2. kube-proxy 将执行 SNAT,将源 IP 映射到本地节点的以太网地址,并使用 DNAT 将服务地址映射到 Pod IP 地址(位于节点 1 上)。

  3. 数据包被发送到节点 1(因为它会响应 Pod IP 地址的 ARP 请求)。K8s 端点(包含与服务选择器匹配的任何 Pod 的 IP 地址)用于将数据包发送到 Pod。

  4. Pod 响应会根据源 IP 地址返回给节点 3,然后根据 kube-proxy 维护的源端口映射将其映射回客户端。

AWS 网络是规定性的,它将 K8s 网络配置为与 AWS VPC 网络协同工作,这对默认情况下 EKS 网络的工作方式有很大影响。下一节将快速回顾 AWS VPC 网络如何工作,以及在深入了解 EKS 网络时需要理解的一些概念。

理解基本的 AWS 网络

在讨论 EKS 网络之前,我们将快速回顾 AWS 中的基本 VPC 网络。当你注册 AWS 账户时,你将获得一个可以跨多个区域以及每个区域内多个可用区AZ)部署服务的账户。区域是一个地理位置,如伦敦、法兰克福或俄勒冈,每个区域由多个 AZ 组成,每个 AZ 包含两个或多个通过高速网络互联的 AWS 数据中心。AZ 是 AWS 网络可靠性的基本单位。

图 7.4 – 基本 VPC 结构

图 7.4 – 基本 VPC 结构

VPC 是一个区域构造,它由一个 IP 10.1.0.0/16 定义。子网是从 VPC 中分配的,并映射到一个可用区(AZ)。具有 IP 地址的服务,如 EKS,分配到一个子网(或多个子网),AWS 平台将从子网范围内分配一个可用的 IP 地址,并在该子网中创建一个弹性网络接口ENI)。在大多数 AWS VPC 中,使用 RFC1918,即私有地址,这意味着 VPC 的 CIDR 范围来自以下子网:

  • 10.0.0.010.255.255.25510/8 前缀)

  • 172.16.0.0172.31.255.255172.16/12 前缀)

  • 192.168.0.0192.168.255.255192.168/16 前缀)

此外,VPC 现在可以使用非 RFC1918 地址,即100.64.0.0/10198.19.0.0/16范围,EKS 支持这些地址。在大型企业中,这些地址范围会在现有的数据中心和办公地点之间共享,因此通常会将一小部分地址分配给 AWS 平台,然后在多个 VPC 和 AWS 服务(包括 EKS)之间共享。可以向 VPC 添加额外的 IP 范围,即二级地址,但一旦范围设置完成,就不能更改。在前面的示例中,添加了一个额外的范围100.64.0.0/10,并在三个不同的可用区(AZ)中从该范围创建了三个额外的子网。在 VPC 中,任何 IP 范围,无论是主地址还是次级地址,都是可路由的。在前面的示例中,子网10.1.1.0/24上的主机可以路由到任何其他子网,包括100.64.0.0/16;然而,AWS 的安全组SGs)和/或网络访问控制列表NACLs)控制哪些系统可以与其他系统进行通信。

需要三个额外的服务来允许访问互联网以及从互联网访问。互联网网关IGW)允许公共 IP 地址与 VPC 地址之间的映射(进入和退出流量)。

NAT 网关NATGW)可以使用 IGW 提供仅出站的访问,当应用程序/系统需要访问公共 AWS API(例如 EKS API)或公共服务(如 Docker Hub)以拉取容器镜像,但不希望被互联网上的任何事物访问时,会使用 NATGW。也可以使用私有 NATGW,简单地将私有子网的 NAT 地址转换为私有地址,而与 IGW 无关。这通常用于在其他地方(本地或 AWS 云的其他部分)重复使用的地址范围之间进行转换,或者用于不在本地路由的地址范围。

传输网关TGW)用于在其他 VPC(同一账户或其他 AWS 账户中的 VPC)之间进行路由,并连接到本地工作负载和服务(通过 VPN 或 Direct Connect 专用连接)。

理解 EKS 网络

现在我们已经理解了基本的 K8s 网络模型、CNI 是什么以及 VPC 网络是如何工作的,我们可以探讨 EKS 网络是如何工作的。VPC CNI 有几个配置选项;本节将不会覆盖所有可能的配置,仅讨论最常见的配置。

EKS 是一个托管服务,控制平面由 AWS 在单独的 VPC 中管理。配置集群时,您需要问的两个主要网络问题是:如何从 kubectl(和其他)客户端访问 API 端点?以及我的 Pods 如何访问或被访问其他系统?我们在第六章中讨论了公共和私有端点,因此在本章的其余部分,我们将重点关注 Pod 网络。让我们从一个基本的 EKS 部署开始,这是一个拥有两个 EC2 实例的私有集群,属于同一节点组。该集群已配置为连接到两个私有 VPC 子网;节点组也被部署到这两个子网中。

图 7.5 – EKS 网络 (基础)

图 7.5 – EKS 网络 (基础)

如果你查看图 7.5中的 VPC,可以看到四个接口(ENI)——每个工作节点一个,两个(通常)用于 EKS 集群——以及一个映射服务器名称到这两个集群 ENI 的私有托管区域。还有两个安全组,一个用于工作节点,一个用于 EKS 控制平面/API。目前,这一切都是默认的 AWS 平台行为。每个 ENI 都已从其所附加的子网中分配了一个 IP 地址。安全组将相互引用,并允许工作节点与 API 之间的访问。

EKS 部署时默认使用 AWS VPC CNI 作为集群的 CNI。可以使用其他 CNI,其中一些在第九章《EKS 高级网络》中有所介绍。vpc-cni 与 kubelet 代理协同工作,从 VPC 请求并映射 IP 地址到主机使用的 ENI,然后将其分配给 Pod。每种 EC2 实例类型可以分配的 EC2 ENI 数量以及可分配给 Pod 的 IP 地址数量是有限的。例如,m4.4xlarge 节点最多可以有 8 个 ENI,每个 ENI 可以有最多 30 个 IP 地址,这意味着理论上每个工作节点可以支持最多 120 个地址(稍后我们将看到,这个数字也有一些限制)。

这种方法的优势在于 Pod 是 AWS VPC 中的第一类公民。Pod 和 EC2 实例之间没有区别;Pod 网络的行为与本章中描述的完全一致。另一个好处是,当流量离开节点时:流量可以通过 AWS 的网络网关和控制来路由和控制,这些网关和控制也用于 AWS 中的所有其他服务。

这种方法的缺点是,由于 Pods/容器的临时性质,EKS 集群可能会迅速耗尽所有可用的子网地址,导致无法部署新的 Pods 和/或其他 AWS 服务(如数据库 RDS)。如果你拥有较小的 VPC 或子网 IP(CIDR)范围,这将特别成问题。有几种方法可以缓解这个问题。

不可路由的次级地址

不可路由的概念是使用本地已有的范围,或理想情况下使用 AWS 用于 Pod 地址的新非 RFC1918 范围,这些范围在本地没有路由,从而允许使用更大的地址范围。这一点在图 7.6中有展示。

图 7.6 – 不可路由的 Pod 网络

图 7.6 – 不可路由的 Pod 网络

图 7.6中,展示了两个不同的 IP 区域或路由域。10.1.0.0/16 和第二个范围 100.64.0.0/10 都可以与 10.0.0.0/8 企业网络进行通信。

100.64.0.0/10 范围内的地址是私有的且不可路由的。它们通过 NATGW 使用,这意味着所有出站流量在离开 100.64.0.0 子网时都会基于源地址进行 NAT,因此这些 IP 地址永远不会出现在 VPC 之外。

任何分配了100.64.x.x范围地址(路由域 2)的 Pods 都无法从企业网络访问,TGW 也不会广告这十条路由。

前缀寻址

EC2 工作节点的默认行为是根据分配给 ENI 的 IP 地址数量以及附加到 Amazon EC2 节点的网络接口数量来分配可用于分配给 Pods 的地址数量。例如,m5.large 节点最多可以有 3 个 ENI,每个 ENI 可以有最多 10 个 IP 地址,因此根据以下计算,它可以支持最多 29 个 Pods:

3 个 ENI(10 个 IP 地址 -1)+ 2(每个节点的 AWS CNI 和 kube-proxy Pods)= 每个节点 29 个 Pods

版本 1.9.0 或更高版本的 Amazon VPC CNI 支持 前缀分配模式,允许你在 /28 IPv4 地址前缀上为每个主机 ENI 运行更多的 Pods,只要你的 VPC CIDR 范围中有足够的空间:

3 个 ENI(每个 ENI 9 个前缀 *每个前缀 16 个 IP 地址)+ 2 = 每个节点 434 个 Pods

然而,请注意,Kubernetes 可扩展性指南建议每个节点的最大 Pod 数为 110 个,在大多数情况下,这将是 CNI 强制执行的最大值。前缀寻址可以与不可路由地址一起使用,因为它仅在 VPC CIDR 能够从 VPC CIDR 中分配连续的 /28 子网时有效。

IPv6

另一种选择是使用 IPv6 替代 IPv4。关于 IPv6 和 IPv4 之间差异的详细讨论超出了本文的范围,但在 VPC 中,如果启用 IPv6,则会自动获得一个公共的 /56 IPv6 CIDR 块,并且每个子网都会分配一个 /64 范围。这提供了每个子网 2⁶⁴(大约 18 千万亿)个 IPv6 地址,因此你永远不会耗尽 IP 范围。如果集群配置为使用 IPv6,则每个 Pod 都会分配一个原生的 IPv6 地址,用于 Pod 到 Pod 之间的通信,IPv6 IGW(仅出口)则用于 IPv6 的互联网访问。

由于大多数环境将支持 IPv6 和 IPv4 的混合使用,EKS 实现了一个 主机本地 CNI 插件,与 VPC CNI 配合使用,支持只有 IPv6 地址的 Pods 连接到集群外部的 IPv4 端点(仅出口)。IPv6 明确解决了 IP 分配问题,但也引入了更多的复杂性,因为你需要管理 IPv4 NAT,并且需要仔细考虑。IPv6 的详细讨论请参见 第九章EKS 高级网络

在本节中,我们概述了原生 K8s 网络的工作原理,以及 EKS/VPC 网络的不同之处。在接下来的章节中,我们将详细回顾如何配置和管理 EKS 网络。

使用 VPC CNI 配置 EKS 网络

如前所述,AWS VPC CNI 默认安装,但你可能需要升级 CNI 以使用前缀分配模式,或者更改配置参数。以下部分将引导你完成常见任务的配置步骤。

管理 CNI 插件

执行 CNI 升级到新集群的最简单方法是应用新的 Kubernetes 清单。以下代码片段将版本 v1.9.1 安装到你的集群中,并根据需要更改版本。然而,请注意,降级 CNI 版本可能会非常棘手,在某些情况下,可能无法成功降级!

在脚本或 CI/CD 流水线中,能够导出当前运行的 CNI 版本(只要它已经部署)通常是一个好主意。以下代码片段可以帮助你做到这一点:

$ export CNI_VER=$(kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2 | sed -e 's/amazon-k8s-cni-init:\(.*\)-eksbuild.1/\1/')
$ echo $CNI_VER
v1.11.3 amazon-k8s-cni:v1.11.3-eksbuild.1

现在我们可以使用以下命令部署 CNI:

$ kubectl apply -f https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/v1.9.1/config/v1.9/aws-k8s-cni.yaml

要在 CNI 配置中启用前缀分配,可以使用以下命令(这对于任何 CNI 配置参数都有效):

$ kubectl set env daemonset aws-node                            -n kube-system ENABLE_PREFIX_DELEGATION=true

EKS 集群还支持使用附加组件,这使你能够配置、部署和更新操作软件,或者提供支持 Kubernetes 应用程序的关键功能,如 VPC CNI。附加组件是构建完成后以及当你有正在运行的工作负载时管理集群的首选方式。创建附加组件最简单的方法是使用 eksctl 工具,如下所示:

$ eksctl create addon --name vpc-cni --version $CNI_VER --cluster $CLUSTERNAME –force

这将创建一个附加组件(在 AWS 控制台中可见)。如果你运行 kubectl get 命令,如下所示,你可以看到管理字段:

$ kubectl get daemonset/aws-node --namespace kube-system --show-managed-fields -o yaml

你应该能够在 YAML 中看到由 EKS 控制平面管理的字段,即 managedFields 键下的输出,如下所示:

..
managedFields:
  - apiVersion: apps/v1
    fieldsType: FieldsV1
    fieldsV1:

查看插件的更简单方式是使用 eksctl 命令:

$ eksctl get addons --cluster $CLUSTERNAME --region $AWS_REGION

这将输出类似以下代码的内容:

vpc-cni v1.9.1-eksbuild.1       ACTIVE  0       arn:aws:iam::119991111:role/eksctl-mycluster-addon-vpc-cni-Role1-4454        v1.10.2-eksbuild.1,v1.10.1-eksbuild.1,v1.9.3-eksbuild.1

这告诉我们有可用的更新版本:v1.10.2、v1.10.1 和 v1.9.3。所以,如果我们想要升级 CNI,可以执行以下命令:

$ eksctl update addon --name vpc-cni --version 1.9.3 --cluster $CLUSTERNAME --region $AWS_REGION --force

禁用 CNI 源 NAT

当 Pod 网络流量的目标是 VPC 外的 IPv4 地址时,默认情况下,AWS_VPC_K8S_CNI_EXTERNALSNAT 变量的值默认为 false

如果你想使用外部 NAT 设备,例如 AWS NATGW,你需要使用以下命令禁用此行为:

$ kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_EXTERNALSNAT=true

配置自定义网络

当 Pod 被创建时,它们的 ENI 将使用节点的主网络接口的安全组和子网。自定义网络允许在同一个 VPC 内使用不同的安全组或子网,我们已经描述了一个需要此配置的用例(不可路由的二级地址)。要启用自定义网络,首先需要在 VPC 中配置所需的安全组和子网。然后你可以运行以下命令:

$ kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true

您需要创建一个 ENIConfig 文件来定义所需的子网和安全组;下面显示了一个示例。请注意,名称设置为子网所在的 AZ;这是一种最佳实践,允许 EKS 根据节点/AZ 组合自动分配正确的子网:

apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
  name: eu-central-1a
spec:
  securityGroups:
    - sg-67346437643864389
  subnet: subnet-7847489798437

此配置使用 kubectl apply -f eu-central-1a.yaml 命令应用(假设你已经将文件命名为文件中 metadata 部分中的相同资源)。然后,您可以应用以下命令自动映射到正确的 topology.kubernetes.io/zone 标签:

$ kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone

让我们看看一些常见的 EKS 网络问题以及如何解决它们。

常见的网络问题

网络通常是一个复杂的问题,尽管 K8s 定义了一个标准模型,每个 CNI 引入了不同的问题。接下来,我们将看看如何解决与 VPC CNI 相关的一些更常见的问题。

问题 解决方案
我的 worker nodes 无法加入集群。 检查 worker nodes 子网是否具有访问互联网的 IP(通过 IGW 或 NATGW)以及访问 EKS API ENI 的权限。检查路由表和相关的安全组以确保。
我的 Pods 无法从 VPC 分配 IP 地址。 检查 VPC 中是否有足够的空闲 IP 地址,如果没有,请分配一个次要 CIDR 范围。一旦有了 IP 地址,启用前缀寻址,或者增加 EC2 实例的大小(更多 ENIs)。
Pods 无法解析 K8S DNS 名称。 确保所有 worker node 子网没有阻止出站或入站 UDP 端口 53 的安全组或网络 ACL,并确保你的 VPC 的 enableDNSHostnamesenableDNSSupport 设置为 true
AWS 负载均衡器无法部署。 确保 worker node 子网标记为 kubernetes.io/role/elbkubernetes.io/role/internal-elb 中的一个。

在本节中,我们看了配置和管理 VPC CNI 所需的详细命令。现在我们将回顾本章的关键学习要点。

概要

在本章中,我们探讨了网络的基本概念和原生 Kubernetes 中的网络模型,以及 EKS 的不同之处。我们描述了 EKS 如何配置 AWS VPC CNI,该 CNI 与 AWS VPC 集成,从 VPC 为 Pod 分配 ENI 和 IP 地址。

我们还学到,在 EKS 中,Pod 是本地 VPC 的成员,流量可以使用 VPC 网络设备,如 Internet Gateway、Transit Gateway 和 NAT Gateway,并且可以通过 VPC 网络控制(如 SG 和/或 NACLs)进行控制。然而,这可能会带来一些挑战,比如 VPC IP 耗尽问题。我们讨论了一些处理 IP 耗尽的方法,包括非路由子网、前缀寻址和 IPv6。

最后,我们讨论了执行常见任务,如管理和升级 CNI,禁用 CNI 源 NAT,以便使用外部 NAT 设备(例如 AWS NATGW),以及配置自定义网络,以便 Pods 可以使用其他 SG 或子网连接到主工作节点,从而帮助提高安全性或应对 IP 耗尽问题。

在下一章中,我们将讨论 EKS 托管节点组,它们是什么,以及如何配置和管理它们。

进一步阅读

第八章:在 EKS 上管理工作节点

在前几章中,我们重点介绍了 弹性 Kubernetes 服务 (EKS) 架构和 API,使用 kubectl 和 Helm 部署工作负载。EKS 可以使用 EC2 和 Fargate 托管这些工作负载。在本章中,我们将重点讨论如何配置、部署和管理在 EKS 中看到的不同 弹性计算云 (EC2) 配置。我们还将讨论使用 EKS 优化镜像和托管节点组相较于自管镜像和实例的优势。Fargate 配置将在 第十五章 中详细讨论。

但现在,我们将介绍以下主题:

  • 使用 Amazon Linux 启动节点

  • 使用 CloudFormation 启动自管的 Amazon Linux 节点

  • 使用 eksctl 启动自管 Bottlerocket 节点

  • 使用 eksctl 理解托管节点

  • 为 EKS 构建自定义 Amazon Machine Image (AMI)

技术要求

读者应具备 YAML、基础网络知识和 EKS 架构的基础知识。在开始本章之前,请确保以下内容:

  • 您有一个 EKS 集群,并且能够执行管理任务

  • 您可以连接到 EKS API 端点的网络

  • 您的工作站已安装 Amazon Web Services (AWS) CLI 和 kubectl 二进制文件

使用 Amazon Linux 启动节点

在本节中,我们将讨论启动单个 EC2 实例并将其连接到集群所需的内容。然后,我们将在讨论托管节点组时进一步扩展这一点。

使用 Amazon Linux 启动节点的前提条件

工作节点只是一个 EC2 实例,EKS 使用它来实际托管部署在集群上的 Pods。任何 EC2 实例都需要以下内容:

  • 一个 身份与访问管理 (IAM) 角色,允许它与 AWS API(EKS、EC2 等)进行通信

  • 一个安全组,至少允许与 EKS 控制平面通信

  • 一个已安装 Kubernetes 代理(kubelet 等)的操作系统镜像

  • 一个 init/boot 脚本,用于注册到特定的 EKS 集群

IAM 角色和权限

每个工作节点和 EC2 实例都需要附加一个 IAM 角色,允许与 AWS EKS API、弹性容器注册表 (ECR) 和 EC2 API 进行通信。需要应用三种 AWS 管理策略:

  • AmazonEKSWorkerNodePolicy

  • AmazonEKS_CNI_Policy

  • AmazonEC2ContainerRegistryReadOnly

此外,如果您希望通过 Systems Manager SSH 访问工作节点,您还需要添加以下策略:

  • AmazonSSMManagedInstanceCore

重要提示

必须将工作节点角色添加到 aws-auth ConfigMap 中,以允许实例与集群注册。如果您正在创建自管节点,您需要自己修改 ConfigMap;像 eksctl 或 AWS 控制台这样的工具会为您完成这些更改。

安全组

每个 EC2 弹性网络接口ENI)都需要至少与一个安全组关联。工作节点通常会分配自己的安全组,从而确保可以控制对这些节点的网络访问。

通常,EC2 工作节点会在主 EKS 集群的安全组中引用一个安全组,该安全组控制对控制平面 ENI 的访问,并允许创建 Pod

AWS AMI

EC2 AMI 是任何 EC2 实例使用的基础镜像,包含操作系统(Windows 或基于 Linux 的系统),通常还包括一组工具,用于使 EC2 实例在 AWS 中正常工作。EKS 支持多种 AMI,但在本节中,我们将讨论两种:Amazon LinuxBottlerocket

亚马逊 EKS 优化的 Amazon Linux AMI

亚马逊 EKS 优化的 Amazon Linux AMI 建立在 Amazon Linux 2 之上,并配置为与 Amazon EKS 一起使用,包括 Docker(在 AMI 的后续版本中,Docker 被 containerd 替代)、kubelet 和 AWS IAM 认证器。

您需要将 AMI 与 Kubernetes 的版本对齐;可以使用以下网址来查找正确的镜像 ID:https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami.html。例如,对于 x86 架构,Kubernetes 1.21 版本,在eu-central-1区域,AMI ID 是ami-03fa8a7508f8f3ccc

亚马逊 EKS 优化的 Bottlerocket AMI

Bottlerocket 是一个基于 Linux 的开源操作系统,由 AWS 专为运行容器而设计。与容器一样,它仅包含运行容器所需的最小包,从而减少了节点本身的攻击面。EKS 优化的 Bottlerocket 已配置为与 Amazon EKS 一起使用,并包括 containerd,在eu-central-1区域,AMI ID 是ami-0674d57b3d6b6ef14

引导脚本

需要一个引导脚本来配置各种代理和 EC2 元数据,并且只需运行一次。引导脚本由 AWS 发布,可以在以下链接找到:github.com/awslabs/amazon-eks-ami/blob/master/files/bootstrap.sh。它需要集成到 EC2 启动过程中或手动运行。

将所有内容整合在一起并创建一个独立的工作节点

可以仅创建一个单独的 EC2 工作节点,但在实际操作中,您始终需要使用 EC2 自动扩展来恢复故障并扩展工作节点。以下步骤仅用于说明您需要执行的活动,以使工作节点启动并运行,并且仅应作为示例使用:

  1. 假设我们已经有一个运行中的集群并且有一些工作节点(如果没有,请参见第三章),请记下现有节点组/EC2 实例所使用的安全组、IAM 角色、VPC 和子网。

  2. 运行以下命令,替换掉 步骤 1 中收集的属性值,以创建 EC2 工作节点。请注意,image-id 的值是与区域和 Kubernetes 版本相关的,因此你可能需要对其进行修改:

    $ aws ec2 run-instances --image-id ami-03fa8a7508f8f3ccc
    --count 1 --instance-type t3.large --key-name <my-key> \
    --security-group-ids <worker-node-sg-id> \
    --subnet-id <subnet-id> --iam-instance-profile Name=<instance-profile-name> \
    --tag-specifications \
     'ResourceType=instance,Tags=[{Key=Name,Value=standalone-worker}]'
    
  3. 一旦实例启动并可用,你可以使用 AWS 会话管理器或使用你在 步骤 2 中指定的 SSH 密钥通过 SSH 连接到它。接着,你应该验证是否有权限获取集群描述,使用以下命令:

    $ export AWS_DEFAULT_REGION=<myregion>
    $ aws eks describe-cluster --name <clustername>
    

重要提示

运行 aws configure 命令并指定默认区域(仅此项)可能更为简便,这样可以确保不同 shell 或登录之间的设置保持一致。

  1. 作为 root 用户,你现在可以下载并运行引导脚本来配置工作节点,使用以下命令:

    $ curl -o bootstrap.sh https://raw.githubusercontent.com/awslabs/amazon-eks-ami/master/files/bootstrap.sh
    $ chmod +x bootstrap.sh
    $ ./bootstrap.sh <clustername>
    
  2. 退出 SSH 会话,回到 Kubernetes 管理机上,运行 kubectl get nodes --watch 命令。你应该看到的是原始工作节点,而不是你刚刚创建的新工作节点。

  3. 在 Kubernetes 管理机上,使用以下命令验证新创建的工作节点是否已在 aws-auth ConfigMap 中配置了角色:

     $ kubectl get cm aws-auth -n kube-system -o json
    
  4. 为了让特定的 EKS 集群能够声明一个工作节点并监控诸如自动扩展事件等事项,你需要在 EC2 实例上添加一个 kubernetes.io/cluster/<clustername> 实例标签。在 AWS 控制台中,导航到你的实例,进入 kubernetes.io/cluster/… 标签,并将其值设置为 owned,然后点击 --watch 命令,应该可以看到新的工作节点正在注册并变为就绪状态。

请参考 docs.aws.amazon.com/eks/latest/userguide/worker.html 获取有关自管理工作节点及其配置的更多详细信息。通常你会使用自动扩展组来支持你的工作节点,因此下一个示例将使用一个预创建的 CloudFormation 模板,结合我们在本示例中使用的 Amazon Linux AMI 来进行配置。

使用 CloudFormation 启动自管理的 Amazon Linux 节点

AWS 提供了一个托管在 s3.us-west-2.amazonaws.com/amazon-eks/cloudformation/2020-10-29/amazon-eks-nodegroup.yaml 的 CloudFormation 脚本,它也可以用来创建自管理节点组。让我们来看一下它是如何工作的!

  1. 在 AWS 控制台中,选择 CloudFormation 服务并点击 创建堆栈 按钮,如下图所示:

图 8.1 – 启动 CloudFormation 堆栈

图 8.1 – 启动 CloudFormation 堆栈

  1. nodegroup.yaml 文件的 Amazon S3 URL 框中输入,并点击 下一步

图 8.2 – 创建堆栈窗口

图 8.2 – 创建堆栈窗口

  1. 现在,系统将要求你配置堆栈属性;这些属性大多数与之前的示例相同。接下来展示的是常用参数的子集:
参数名称 描述
ClusterName 现有集群的名称。如果名称不正确,节点将无法加入集群。
ClusterControlPlaneSecurityGroup 集群控制平面使用的安全组。
NodeGroupName 节点组的唯一名称。
NodeAutoScalingGroupMinSize 自动伸缩组中的最小节点数量。
Desired capacity of Node Group ASG 自动伸缩组中所需的节点数量。
NodeAutoScalingGroupMaxSize 自动伸缩组中节点的最大数量。设置为至少比所需容量大 1 个。
NodeInstanceType 工作节点的 EC2 实例类型。
NodeVolumeSize 工作节点弹性块存储EBS)卷的大小。
KeyName 允许 SSH 访问实例的 EC2 密钥对。
DisableIMDSv1 设置为truefalse
VpcId 工作实例的 VPC。
Subnets 可以创建工作节点的子网。

表 8.1 – CloudFormation 参数列表

由于此模板使用了自动伸缩组,你需要指定自动伸缩组的最小、最大和所需容量(更多细节请参考docs.aws.amazon.com/autoscaling/ec2/userguide/asg-capacity-limits.html)。请注意,安全组是为集群 API 定义的,因为模板为自动伸缩组中的实例创建了新的安全组。

  1. 输入参数(使用之前示例中的参数),然后通过堆栈工作流程点击,直到堆栈开始部署。一旦堆栈部署完成,你可以使用kubectl get nodes命令来验证节点是否已经注册。同样,节点不应当可见,但这次是一个不同的问题。

  2. 此模板会创建一个新的 IAM 角色,需要将其添加到aws-auth文件中。你可以从 Kubernetes 管理员工作站使用kubectl edit cm aws-auth -n kube-system命令编辑 ConfigMap,并将以下条目添加到mapRoles键中,其中<rolearn>是分配给 EC2 实例的角色:

mapRoles: |
- groups:
 - system:bootstrappers
 - system:nodes
 rolearn: arn:aws:iam::<ACCOUNTID>:role/<instanceROLE>
 username: system:node:{{EC2PrivateDNSName}}
  1. 一旦你添加了角色并部署了 ConfigMap,你可以使用以下命令查看节点注册并准备好供调度器使用:
$ kubectl get nodes --watch

Amazon Linux 基于标准 Linux 内核,而 Bottlerocket 是从零开始构建的,旨在支持容器。在下一节中,我们将探讨如何基于 Bottlerocket 操作系统部署自管节点,它提供了更好的容器支持。

启动自管 Bottlerocket 节点与 eksctl

Bottlerocket 正因其作为运行容器工作负载的安全平台而日渐流行。其关键优势之一是运行两个操作系统分区,这意味着更简单地进行升级并将停机时间最小化。这在第十章升级 EKS 集群中详细讨论。

到目前为止,我们已经使用 AWS CLI、控制台和预制的 CloudFormation 模板创建了托管节点。eksctl是由Weaveworks和 AWS 联合开发的工具,它将基于配置文件或 CLI 选项生成和部署 CloudFormation 堆栈。您可以使用以下 URL 安装它:docs.aws.amazon.com/eks/latest/userguide/eksctl.html

在 eksctl 版本 0.40.0 之前,您只能修改使用 eksctl 创建的集群。然而,较新的版本允许对未使用 eksctl 创建的集群执行一些操作——这包括添加节点组。

我们将使用现有集群(见第三章构建您的第一个 EKS 集群)并添加两个自管理的 Bottlerocket 节点。使用以下配置文件;请注意,它被分成多个部分以提高可读性:

---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: myclusterName
  region: eu-central-1
  version: '1.21'
vpc:
  id: "vpc-123454"
  subnets:
     private:
        private1:
           id:  "subnet-11222"
        private2:
           id:  "subnet-11333"
  securityGroup: "sg-4444444"

在前面的部分中,我们定义了集群nameregionversion值,以及 VPC 详细信息,因为我们将重用现有的 VPC。在接下来的部分中,我们定义一个节点组。如果您使用的是一组私有子网(没有Internet 网关(IGW)),请确保将privateNetworking键设置为true;否则,部署将失败!

iam:
  withOIDC: true
nodeGroups:
  - name: ng-bottlerocket
    instanceType: m5.large
    privateNetworking: true
    desiredCapacity: 2
    amiFamily: Bottlerocket
    ami: auto-ssm
    iam:
       attachPolicyARNs:
          - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
          - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
          - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
          - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
    ssh:
        allow: true
        publicKeyName: mykeypair
    subnets:
      - subnet-11222
      - subnet-11333

重要提示

由于这是使用现有集群,必须包含vpc:部分。securityGroup键指的是集群安全组,而不是工作节点安全组。由于这也是一个私有集群,必须包含privateNetworking: true键值对。nodegroups关键字用于自管理节点。请调整配置文件(键以code style表示)并将其保存为bottlerocket.yaml

安装了 eksctl 并保存了配置文件后,可以运行以下命令使用配置文件创建集群:

$ eksctl create nodegroup --config-file=bottlerocket.yaml

然后,使用以下命令查看节点注册并准备好调度:

$ kubectl get nodes --watch

请注意,eksctl 将标记节点并修改aws-auth ConfigMap,因此只要eksctl命令成功,节点将自动注册并变为可用。

自我管理的节点组适用于你有很多自定义操作系统配置或需要特定 AMI 的情况。如果你需要进行任何节点更新,你将负责排空节点、移动 Pods、调整调度程序逻辑和替换节点。而托管节点组则可以让你通过点击一个按钮(或通过 API 调用)完成这些操作,因此通常应该优先选择托管节点组。在下一部分中,我们将看看如何改用托管节点组。

使用 eksctl 理解托管节点

托管节点组利用自动伸缩组提供执行工作节点升级和修改的基本功能。每个自动伸缩组指定一个启动模板,启动模板定义了配置选项。

如果你更换启动配置——例如更改 EKS AMI ID——那么创建的任何新实例将使用新的启动模板,从而使用新的 AMI。你可以终止旧实例,自动伸缩组将自动使用新的启动模板替换它们。对于托管节点组,此过程是自动化的,EKS 控制平面为托管节点组执行以下步骤:

  1. 它会随机选择一个节点并排空该节点上的 Pods。

  2. 它会在每个 Pod 被驱逐后对节点进行隔离,以确保 Kubernetes 调度程序不会向该节点发送新的请求,并将该节点从活跃节点列表中移除。

  3. 它会向自动伸缩组发送该节点的终止请求,这将触发使用新的启动模板/AMI 部署新节点。

  4. 它会重复执行步骤 1-3,直到节点组中没有使用早期版本启动模板部署的节点。

让我们使用以下命令部署一个新的托管节点组:

$ eksctl create nodegroup --config-file=managed-ng.yaml

然后,使用以下命令查看节点是否被注册并准备好供调度程序使用:

$ kubectl get nodes --watch

以下配置文件应使用;为了可读性,这里将其分成多个部分:

重要提示

你可以使用先前的模板,但确保将 nodeGroups: 键更改为 managedNodeGroups:。你还需要移除特定于 Bottlerocket 的 amiFamily:ami: 键,并(可选地)添加 labels: 键。

---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: myclusterName
  region: eu-central-1
  version: '1.21'
vpc:
  id: "vpc-123454"
  subnets:
     private:
        private1:
           id:  "subnet-11222"
        private2:
           id:  "subnet-11333"
  securityGroup: "sg-4444444"
iam:
  withOIDC: true

上述部分与非托管节点组没有不同;我们定义集群和 VPC 信息(使用现有的 VPC)。在下一部分,我们将 nodeGroups 键替换为 managedNodeGroups 键,并添加一个标签:

managedNodeGroups:
  - name: managed-ng
    labels: { role: workers }
    instanceType: m5.large
    privateNetworking: true
    desiredCapacity: 2
    iam:
       attachPolicyARNs:
          - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
          - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
          - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
          - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
    ssh:
        allow: true
        publicKeyName: mykeypair
    subnets:
      - subnet-11222
      - subnet-11333

一旦节点注册完成,如果你进入 AWS 控制台,选择 EKS | 集群 | mycluster,并进入 计算 标签页,你将看到托管节点组已注册并显示为 活动 状态。在下面的截图中,你可以看到新节点组和旧节点组,后者使用较旧的 AMI 版本:

图 8.3 – 节点组窗口

图 8.3 – 节点组窗口

现在,您可以通过点击立即更新链接自动升级(包括 kubelet、containerd 等)。该过程在第十章升级 EKS 集群中有更详细的讨论。

到目前为止,我们一直使用标准 AMI,没有进行任何定制。在本章的最后部分,我们将探讨如何为 EKS 构建一个自定义 AMI,如果您需要加固操作系统或进行某些内核更改时,可能需要使用该自定义 AMI。

为 EKS 构建自定义 AMI

有许多原因可能需要您使用自定义 AMI,例如安全加固、Kubernetes 代理二进制文件更新等。您可以通过多种方式实现这一点,但我们将讨论如何使用 HashiCorp 的 Packer(https://learn.hashicorp.com/packer),它是一个开源工具,可用于创建多种类型的操作系统镜像。以下是步骤:

  1. 在第一步中,我们需要使用以下链接在工作站上安装 Packer:learn.hashicorp.com/collections/packer/aws-get-started。这将使我们能够创建自定义 AMI。

  2. 然后,您可以克隆以下 Git 仓库并切换到新的amazon-eks-ami目录:github.com/awslabs/amazon-eks-ami。这是创建官方 AMI 时使用的相同过程。

  3. 从克隆的仓库根目录,您现在可以运行接下来的make命令来创建一个基本的1.21 AMI(确保您使用的区域已配置了默认的 VPC):

    $ make 1.21 aws_region=<yourRegion>
    
  4. 启动一个新的 EC2 实例需要 15-20 分钟。使用 SSH 从 Packer 机器连接,然后使用/scripts目录中的脚本配置该实例。配置完成后,将 EBS 卷转换为 AMI,并终止该实例。

  5. 您可以使用以下命令验证镜像是否存在:

     $ aws ec2 describe-images --owners self --output json --region <yourRegion>
    

为了定制构建,您可以修改 makefile、Packer 构建文件(eks-worker-al2.json),以及/或添加/修改/scripts目录中的脚本。这需要详细了解 Packer 和 Linux,因此超出了本书的范围,但在以下链接中有一篇有用的文章,详细描述了部分定制内容(您需要 AWS 登录才能访问):aws.amazon.com/premiumsupport/knowledge-center/eks-custom-linux-ami/

现在我们已经查看了配置和部署 EC2 工作节点的多种方式,接下来我们将回顾本章的关键学习点。

总结

在本章中,我们探讨了任何基于 EC2 的工作节点的基本要求,包括配置 IAM 角色、Kubernetes 代理(kubelet 等)以及安全组以允许与 EKS 控制平面端点的通信。

接着,我们学习了如何使用 Amazon Linux 和 Bottlerocket(AWS 开发的安全容器操作系统)AMI,通过 AWS 控制台/CLI、CloudFormation 和 eksctl 创建自管理节点组。理解选择操作系统时有多个选项非常重要,从 Amazon EKS 优化的 Linux 和 Bottlerocket 到完全自定义的操作系统。Amazon Linux 是最简单的操作系统选择,因为这些镜像是由 AWS 创建和管理的,并且如果需要更改,它还允许访问标准 Linux 内核。Bottlerocket 更加安全,但其架构与标准 Linux 内核有很大不同,因此需要更多的培训和设计投资。如果你有一些非常具体的加固要求或使用特定的管理工具,那么你需要使用自定义 AMI。接着,我们讨论了托管节点组如何简化更新工作节点操作系统和 Kubernetes 代理的操作负担,并展示了如何通过一些简单的更改,使用 eksctl 部署它们。

最后,我们简要探讨了如何使用 HashiCorp 的 Packer 和 AWS 存储库创建自定义 AMI,以支持更加定制化的基于 EC2 的工作节点。

在下一章中,我们将探讨升级集群的整体过程,并在此基础上扩展前几章中讨论的一些概念。

进一步阅读

第九章:与 EKS 的高级网络配置

在前几章中,我们回顾了标准的 AWS 和 EKS 网络配置(第七章)。然而,在某些情况下,你需要使用本章中将描述的一些更高级的网络功能。

本章将探讨一些使用案例,例如如何管理 Pod 地址耗尽问题,使用互联网协议版本 6IPv6),以及如何使用网络策略强制实施第三层网络控制以管理 Pod 流量。我们还将讨论如何使用不同的复杂网络信息系统CNI)在 EKS 中支持多个 Pod 网络接口,利用 Multus CNI,以及如何支持加密和网络加速的覆盖网络,如数据平面开发工具包DPDK)或扩展伯克利数据包过滤器eBPF)。这些都是复杂的主题,本章的目标是为集群管理员提供基础知识,以便他们能够评估是否需要配置这些解决方案以及它们对 EKS 部署的影响。

本章将具体涵盖以下内容:

  • 在 EKS 集群中使用 IPv6

  • 安装和使用 Calico 网络策略

  • 选择和使用 EKS 中的不同 CNI

技术要求

你应该熟悉 YAML、基础网络知识以及 EKS 架构。在开始本章之前,请确保你已经具备以下条件:

  • 连接到你的 EKS API 端点的网络连接

  • AWS CLI 和 kubectl 二进制文件已安装在你的工作站上

  • 对 IPv6 地址分配和使用的基础理解

  • 虚拟私有云VPC)网络以及如何创建网络对象,如弹性网络接口ENI)等有较好的理解

在 EKS 集群中使用 IPv6

IPv6 相较于 IPv4 有一些明显的优势,即它提供了更大的地址空间(包括公共 IP 地址),通过移除网络地址转换NAT)跳数来减少一些延迟,并且可以简化整体路由网络配置。它也有一些局限性(不仅限于 EKS,还包括其他 AWS 服务),因此在采用 IPv6 时需要小心。在生产环境中实施之前,请仔细查看 aws.amazon.com/vpc/ipv6/docs.aws.amazon.com/eks/latest/userguide/cni-ipv6.html

目前无法在现有集群上启用 IPv6,因此我们需要做的第一件事是创建一个使用 IPv6 地址族的新集群,该集群至少运行 Kubernetes 1.21。我们将使用 eksctl 和以下配置文件 myipv6cluster.yaml

---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: myipv6cluster
  region: "eu-central-1"
  version: "1.21"
kubernetesNetworkConfig:
  ipFamily: IPv6
addons:
  - name: vpc-cni
    version: latest
  - name: coredns
    version: latest
  - name: kube-proxy
    version: latest
iam:
  withOIDC: true
managedNodeGroups:
  - name: ipv6mng
    instanceType: t3.medium

然后,我们可以运行以下 eksctl 命令来创建和部署集群:

$ eksctl create cluster -f myipv6cluster.yaml

重要提示

这个过程可能需要 15 到 25 分钟才能完成。

由于我们没有指定现有的 VPC,eksctl 将为我们创建一个,并从 Amazon 池中分配额外的 IPv6 无类域间路由 (CIDR) 范围(每个 VPC 需要一个 IPv4 CIDR 范围),如下所示:

图 9.1 – eksctl VPC

图 9.1 – eksctl VPC

如果我们查看分配给此 VPC 的子网,我们可以看到 eksctl 创建了六个子网,其中三个是公共的,三个是私有的(每个 可用区AZ)有两个子网)。它还从 VPC 分配的主范围中为 IPv4(必需)和 IPv6 分配了 CIDR 范围:

图 9.2 – eksctl IPv4/v6 子网

图 9.2 – eksctl IPv4/v6 子网

集群将与两个工作节点一起创建(默认情况下在公共子网上)。每个工作节点都有 IPv4 和 IPv6 地址,因为 VPC 需要 IPv4:

$ kubectl get nodes -o wide

输出类似于以下内容:

NAME  STATUS   ROLES    AGE   VERSION  INTERNAL-IP EXTERNAL-IP
ip-192-168-3-11.eu-central-1.compute.internal Ready <none>   27h   v1.21.12-eks-5308cf7   2a05:d014:ec6:2f00:4e36:b47b:c13f:cb1f   52.58.98.70
ip-192-168-90-100.eu-central-1.compute.internal   Ready    <none>   27h   v1.21.12-eks-5308cf7   2a05:d014:ec6:2f02:2d4c:d5df:4eb5:cb86   18.195.1.196

IPv6 前缀分配发生在每个工作节点启动时。每个工作节点的 ENI 被分配一个 IPv6 /80 前缀(/80 => ~10¹⁴ 地址),这个前缀足够支持拥有数百万个 Pod 的大规模集群,尽管当前 Kubernetes 推荐每个主机不超过 110 个 Pod。

您可以通过查看 EC2 实例并点击网络选项卡来查看前缀分配。在以下示例中,已分配了 2a05:d014:ec6:2f00:8207::/80 前缀,且没有分配 IPv4 前缀:

图 9.3 – IPv6 前缀分配

图 9.3 – IPv6 前缀分配

一旦在集群级别启用了 IPv6,您将无法为您的 Pod 分配 IPv4 地址;只能分配 IPv6 地址,因为 EKS 当前不支持同时具有 IPv4 和 IPv6 地址的 Pod(就像它们对待工作节点一样)。然而,如果我们使用 kubectl get pods 命令列出 kube-system 命名空间中的系统 Pod,我们可以看到这些 Pod 被分配了 IPv6 地址:

$ kubectl get pods -n kube-system -o wide
NAME     READY   STATUS    RESTARTS   AGE   IP   …
aws-node-qvqpx  1/1     Running   0   15h   2a05:d014:ec6:2f00:4e36:b47b:c13f:cc1f
aws-node-wkr9g  1/1     Running   1   15h   2a05:d014:ec6:2f02:2d4c:d5df:4eb5:c686
coredns-745979c988-7qqmr 1/1 Running   0   15h   2a05:d014:ec6:2f00:8207::
coredns-745979c988-hzxx8 1/1 Running   0   15h   2a05:d014:ec6:2f00:8207::1
kube-proxy-2rh47        1/1 Running   0   15h   2a05:d014:ec6:2f02:2d4c:d5df:4eb5:c686
kube-proxy-vwzp9     1/1 Running   0   15h   2a05:d014:ec6:2f00:4e36:b47b:c13f:cc1f

我们可以使用 第四章 中的部署 .yaml 文件,在 EKS 上运行您的第一个应用程序,来部署一个简单的 Web 应用。如果我们查看生成的 Pod,我们可以看到它们也有一个 IPv6 地址。以下是一个示例输出:

NAME    READY   STATUS    RESTARTS   AGE
simple-web-99b67d675-4t68q   1/1     Running   0          11s   2a05:d014:ec6:2f00:8207::2
simple-web-99b67d675-vl6hk   1/1     Running   0          11s   2a05:d014:ec6:2f02:b49c::

我们已经了解了如何创建具有 IPv6 地址族的集群,以及如何部署 Pod。然而,Pod 在 VPC 内外如何与 IPv6 通信存在一些基本的差异。接下来的部分将更详细地描述这些网络场景,重点介绍流量如何路由。

重要说明

安全组配置至少需要允许流量。因此,我们将在下一节仅讨论 VPC 路由表的配置。

Pod 到外部 IPv6 地址

IPv6 地址非常大,具有全球唯一性,因此不会执行 NAT。运行在公共子网中并分配了来自子网前缀的 IP 地址的任何 Pod,将能够直接通过互联网网关IGW)路由到互联网,而无需地址转换。这也意味着它们可以从互联网访问(访问由安全组控制)。如果子网是私有的,则可以使用仅出站 IGWEIGW)提供对外部 IPv6 地址的访问,但不允许任何直接的入站 IPv6 流量(与常规 IGW 不同)。

VPC 路由

由于任何 IPv6 Pod 总是使用其 IPv6 地址作为源 IP 地址,因此可以使用标准 Kubernetes 服务发现和路由机制以及 VPC 路由表来访问另一个 Pod。任何 VPC 内部的 IPv6 流量都不会进行源 NAT(与默认的 IPv4 不同),因此目标 Pod 总是能够看到源 Pod 的真实 VPC IPv6 地址。

由于 Pod 仅具有 IPv6 栈,因此只能与其他运行 IPv6 栈并在 VPC 路由表中有条目的元素进行通信。这包括以下内容:

要与 IPv4 端点通信,需要一个转换服务;可以通过多种方式实现:

  • 使用主机本地 CNI 插件

  • 使用 NAT64(AWS NAT 网关)

  • 使用 DNS64(AWS Route 53)

接下来,我们将详细回顾这些选项。

使用主机本地 CNI 插件

这种方法至今仍在使用,即扩展 VPC CNI 以支持一个内部 IPv4 地址范围169.254.172.0/22,该范围分配给每个工作节点,并用于在每个 Pod 中创建一个辅助 IPv4 接口。这个配置包含在我们创建的 IPv6 集群中,可以通过 SSH 进入工作节点并以root身份运行cat /etc/cni/net.d/10-aws.conflist命令查看。

由于 IPv4 169.254.172.0/22范围在 VPC 中不可见,并且被每个工作节点重用,因此所有 IPv4 出站流量都会被源 NATSNAT)到分配给工作节点的 IPv4 ENI;否则,会发生 IP 地址冲突。下图展示了这一解决方案:

图 9.4 – 主机本地解决方案

图 9.4 – 主机本地解决方案

为了查看这个是如何工作的,我们可以创建一个新的容器,并使用以下命令进入它:

$ kubectl run -i --tty busybox --image=busybox --restart=Never -- sh

进入容器 shell 后,可以使用iptablesnetstat命令查看容器中的辅助接口和路由表。输出的内容类似于这里显示的块:

$ ifconfig
eth0
Link encap:Ethernet  HWaddr AA:1D:5F:F0:B6:C7
inet6 addr: 2a05:d014:ec6:2f02:b49c::2/128 Scope:Global
……
v4if0
Link encap:Ethernet  HWaddr 5A:FE:0E:46:EC:D9
inet addr:169.254.172.4  Bcast:169.254.175.255  Mask:255.255.252.0
$ netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         … Iface
0.0.0.0         169.254.172.1   0.0.0.0         … v4if0
169.254.172.0   169.254.172.1   255.255.252.0   … v4if0
169.254.172.1   0.0.0.0         255.255.255.255 … v4if0

这意味着当一个 Pod 尝试与 IPv4 地址通信时,它将使用 v4if0 接口,该接口会进行 SNAT 操作,将流量转换到宿主机的 IPv4 ENI。接下来我们将看看 AWS 在 2021 年宣布的一些增强功能,这些功能能够简化配置并消除宿主机 SNAT 的需求。

使用 DNS64 与 AWS Route 53

每个 VPC 都有内置于子网中的 DNS 解析器(通常位于子网 CIDR 的第二个 IP 地址)。通常它会解析 IPv4 DNS 请求,但现在你可以在子网中添加 DNS64,它会应用于该子网内的所有 AWS 资源。

使用 DNS64 时,AWS Route 53 解析器会根据客户端请求查找 DNS 条目,并根据响应执行以下两种操作之一:

  • 如果 DNS 响应包含 IPv6 地址,解析器会将 IPV6 地址返回给客户端,然后客户端将直接使用 IPv6 地址建立 IPv6 网络会话。

  • 如果响应中没有 IPv6 地址,只有 IPv4 地址,则解析器会通过在记录中的 IPv4 地址上添加常见的 /96 前缀(在 RFC6052 中定义,64:ff9b::/96),将其转换为支持与 NAT64 一起工作的格式(接下来会描述)。

这个图示说明了这一点:

图 9.5 – VPC 上的 DNS64

图 9.5 – VPC 上的 DNS64

让我们来看看如何使用常见的 64:ff9b::/96 前缀与 IPv4 端点进行通信。

使用 NAT64 与 NATGW

第七章EKS 中的网络,我们讨论了如何使用 NAT 网关NATGW)来允许私有子网的出站网络访问。它通过将私有地址(IPv4)映射到分配给 NATGW 的公共 IP 地址来实现这一点。

NAT64 在任何现有或新的 NATGW 上都会自动启用。一旦你为子网启用了 DNS64,并为 64:ff9b::/96 前缀添加了一条路由到 NATGW,接下来会发生以下步骤:

  1. 如前所述,如果没有匹配的 IPv6 记录,VPC 的 DNS 解析器会返回 IPv4 地址和 RFC6052 前缀,客户端会使用此信息建立 IPv6 会话。VPC 路由表会将流量发送到 NAT64 网关,后者通过将 IPv6(Pod)地址替换为 NATGW 的 IPv4 地址来将 IPv6 包转换为 IPv4。

  2. NAT64 网关通过与其子网关联的路由表将 IPv4 数据包转发到目标。

  3. IPv4 目标会将 IPv4 响应包返回给 NATGW。NATGW 会将响应的 IPv4 包转换回 IPv6,通过将其 IP(目标 IPv4)替换为 Pod 的 IPv6 地址,并在源 IPv4 地址前加上 64:ff9b::/96 前缀。然后,数据包将按照路由表中的本地路由声明流向 Pod:

图 9.6 – VPC 上的 NAT64

图 9.6 – VPC 上的 NAT64

重要说明

如果您不打算在接下来的部分中使用 IPv6 集群,别忘了使用eksctl delete cluster myipv6cluster命令删除它。

现在让我们看看如何通过网络策略控制集群内部的流量。

安装并使用 Calico 网络策略

默认情况下,集群中所有命名空间的所有 Pod 可以相互通信。这可能是可取的,但在许多情况下,您可能希望采取最小权限的网络访问方式。幸运的是,Kubernetes 提供了网络策略来限制 Pod 之间的访问(东西向通信)。网络策略操作在 OSI 模型的第 3 层和第 4 层,因此相当于传统的本地防火墙或 AWS 安全组。更多详情请参见kubernetes.io/docs/concepts/services-networking/network-policies/

EKS 的 VPC CNI 不支持网络策略,因此需要网络插件或不同的 CNI。在本节中,我们使用eksctl与以下配置文件myipv4cluster.yaml

---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: myipv4cluster
  region: "eu-central-1"
  version: "1.19"
kubernetesNetworkConfig:
  ipFamily: IPv4
addons:
  - name: vpc-cni
    version: latest
  - name: coredns
    version: latest
  - name: kube-proxy
    version: latest
iam:
  withOIDC: true
managedNodeGroups:
  - name: ipv4mng
    instanceType: t3.medium

重要提示

我们注意到在与 IPv6 集群和较新的 Tigera Operator 兼容性方面遇到了一些挑战,因此在使用这两者时需要小心。

然后,我们可以运行以下eksctl命令来创建并部署集群:

$ eksctl create cluster -f myipv4cluster.yaml

一旦集群激活,如果需要,将集群添加到本地管理员机器中:

$ aws eks update-kubeconfig --name myipv4cluster

接着使用以下命令将calico-operator添加到集群中:

$ kubectl apply -f https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/master/config/master/calico-operator.yaml

然后,我们可以使用kubectl配置 Calico 插件,部署calico-install.yaml配置文件:

apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
  annotations:
    "helm.sh/hook": post-install
spec:
  cni:
    type: AmazonVPC

我们可以使用以下命令验证插件是否已安装:

$ kubectl get tigerastatus/calico
NAME     AVAILABLE   PROGRESSING   DEGRADED   SINCE
calico   True        False         False      11m

我们已经具备了支持网络策略的所有先决条件,接下来让我们使用simple-deployments.yaml清单部署两个简单的部署。文件的这一部分将创建两个命名空间:

---
apiVersion: v1
kind: Namespace
metadata:
  name: deploy1
---
apiVersion: v1
kind: Namespace
metadata:
  name: deploy2

以下代码部分将在deploy1命名空间中创建部署deploy1

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy1
  namespace: deploy1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: deploy1
  template:
    metadata:
      labels:
        app: deploy1
    spec:
      containers:
        - name: busybox
          image: busybox
          imagePullPolicy: IfNotPresent
          command: ['sh', '-c', 'echo Running ; sleep 3600']

接下来的代码部分将在deploy2命名空间中创建第二个部署deploy2

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy2
  namespace: deploy2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: deploy2
  template:
    metadata:
      labels:
        app: deploy2
    spec:
      containers:
        - name: busybox
          image: busybox
          imagePullPolicy: IfNotPresent
          command: ['sh', '-c', 'echo Running ; sleep 3600']

部署完成后,使用以下命令记下deploy1命名空间中 Pod 的 IP 地址(在本示例中为192.168.42.16):

$ kubectl get po -o wide -n deploy1
NAME  READY   STATUS    RESTARTS   AGE   IP    …
deploy1-111  1/1  Running   0 69s   192.168.42.16 ….

现在,您可以使用以下命令进入deploy2命名空间中的 Pod:

$ kubectl exec --stdin --tty <pod-name> -n deploy2 -- sh

如果您在 Shell 中运行以下命令,它会成功执行(将 IP 地址替换为deploy1命名空间中 Pod 的 IP 地址):

$ ping -c 5 192.168.42.16
PING 192.168.42.16 (192.168.42.16): 56 data bytes
64 bytes from 192.168.42.16: seq=0 ttl=254 time=0.110 ms
…..

一旦 ping 操作完成,我们可以部署deny-all.yaml网络策略,该策略会拒绝deploy2命名空间的所有出站流量(出口流量):

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: deploy2
spec:
  podSelector:
    matchLabels: {}
  policyTypes:
  - Egress

如果您在deploy2 Pod 命名空间中运行以下命令,由于网络策略的原因,它将失败

$ ping -c 5 192.168.42.16
PING 192.168.42.16 (192.168.42.16): 56 data bytes

我们现在已经了解了如何通过 Calico 策略引擎扩展 EKS 以支持网络策略。接下来的部分,让我们看看如何进一步修改 CNI,甚至完全替换它。

在 EKS 中选择和使用不同的 CNI

我们已经看到 AWS CNI 如何与 VPC 集成,以提供IP 地址管理IPAM)服务以及 Pod 网络接口的创建和管理。以下是你可能希望替换默认 AWS VPC CNI 的一些原因:

  • 如果你想拥有多个 Pod 接口

  • 如果你想使用覆盖网络进行加密

  • 如果你想使用网络加速,比如 DPDK 或 eBPF

由于 EKS 控制面由 AWS 管理,目前支持的 CNI 数量有限;请参考docs.aws.amazon.com/eks/latest/userguide/alternate-cni-plugins.html获取最新的插件列表。

你需要做出的最重要决定是是否可以像我们使用 Calico 时那样扩展现有的 VPC CNI,但继续使用 VPC CNI 来管理 IP 地址和 Pod 接口。这被称为 CNI 插件链式连接,其中一个主 CNI 通过附加功能得到增强。因此,如果我们查看 EC2 主机上/etc/cni/net.d/10-aws.conflist文件中的默认 CNI 配置文件,我们可以看到默认启用了三个插件:

{"cniVersion": "0.4.0",
  "name": "aws-cni",
  "disableCheck": true,
  "plugins": [
    {
      "name": "aws-cni",
      "type": "aws-cni",
…
pluginLogFile": "/var/log/aws-routed-eni/plugin.log",
…},
    {
      "name": "egress-v4-cni",
      "type": "egress-v4-cni",
      …},
    {
      "type": "portmap",
      "capabilities": {"portMappings": true},
      "snat": true }]

Kubernetes 在添加或删除 Pod 网络接口时会首先调用aws-cni插件,因为它在列表中的位置排在最前面。然后它会调用链中的下一个插件;在默认情况下,这是egress-v4-cni(即在在 EKS 集群中使用 IPv6部分讨论的 IPv4 主机本地分配器),将命令和传递给第一个 CNI 插件的参数加上最后一个插件的结果传递给它,依此类推,直到链完成。这允许通过 CNI 插件配置不同的功能,因此在 EKS 的默认情况下,第一个插件配置 Pod 接口并提供一个 VPC 可路由的 IP 地址。第二个插件用于执行 IPv6 到 IPv4 的转换或源 NAT。

让我们来看一下如何使用 Multus,这是支持的 EKS CNI 之一,它可以在不丧失 VPC 连接的情况下拥有多个 Pod 网络接口。在这种情况下,Multus 将充当一个元插件,并调用其他 CNI 来进行 Pod 网络配置。

这非常有用,因为我们不需要替换标准的 VPC-CNI,因此 IP 地址和路由由 AWS CNI 使用 VPC API 在 VPC 中设置。我们可以使用 Multus 创建第二个 Pod 接口,作为管理接口或用于交换心跳信息,如果 Pods 希望作为集群运行。

配置多个 Pod 网络接口

如下图所示,我们通常将其他 ENI 附加到工作节点,由 Multus 管理,而主(master)EC2 接口始终由 VPC CNI 管理:

图 9.7 – Multus CNI 与 EKS 的集成

图 9.7 – Multus CNI 与 EKS 的集成

我们应该做的第一件事是使用以下命令在我们为网络策略使用的集群上安装 Multus:

$ kubectl apply -f https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/master/config/multus/v3.7.2-eksbuild.1/aws-k8s-multus.yaml

这将创建一个kube-multus-ds DaemonSet,运行在您的工作节点上,在此案例中为2个节点。可以使用以下命令查看:

$ kubectl get ds kube-multus-ds -n kube-system
NAME             DESIRED   CURRENT   READY   …
kube-multus-ds   2         2         2       …

如果我们查看主机的/etc/cni/net.d/ CNI 配置目录,可以看到有一个新的00-multus.conf配置文件。Kubernetes 在启动时会首先使用这个文件,因为它的数值较小(00)。

更详细地查看新的 CNI 配置文件,我们可以看到 Multus 现在是主要的 CNI(由type关键字定义)。它还使用了delegates关键字调用其他 CNI;在此案例中,仅使用了 AWS VPC CNI,它的行为与之前相同,因为插件列表没有变化。以下输出显示了新配置文件的简略视图,除了最初的部分外,其他部分与默认的 AWS VPC CNI 相同:

{
      "cniVersion": "0.3.1",
      "name": "multus-cni-network",
      "type": "multus",
      "capabilities": {
           "portMappings": true},
…
      "delegates": [{
           "cniVersion": "0.4.0",
           "name": "aws-cni",
           "disableCheck": true,
            "plugins": [{
                "name": "aws-cni",
                "type": "aws-cni",
                 ….
           }, {
                "name": "egress-v4-cni",
                "type": "egress-v4-cni",
               …
           }, {
                "type": "portmap",
…
                 },
                 "snat": true }]}]}

如果您还记得,当我们使用eksctl创建集群时,它也创建了一个 VPC 和一些私有子网,接下来展示了这些子网的示例。我们将使用这些子网来托管 Multus 管理的 ENI:

图 9.8 – eksctl 创建的私有子网

图 9.8 – eksctl 创建的私有子网

在启用 Multus 后,我们需要进行一些网络布线,才能确保 Multus 正确工作。我们需要执行以下操作:

  • 为 Multus 创建一些 ENI,给它们打上标签,以便 EKS 忽略它们

  • 将它们附加到我们的工作节点,并整理 IP 地址分配和路由,以便 VPC 可以将流量路由到 Pod 的辅助接口。

创建 Multus ENIs

我们需要做的第一件事是在不同的私有子网中创建 ENI。下面展示的 CloudFormation 脚本提供了如何通过编程方式创建 ENI 的示例,但您也可以使用其他方法。您需要特别注意标签;我们明确声明了接口连接的集群和可用区(zone),并且接口不应由 EKS 控制平面管理。我们还创建了一个安全组,因为这是 ENI 的强制性参数:

AWSTemplateFormatVersion: "2010-09-09"
Resources:
   multusSec:
      Type: AWS::EC2::SecurityGroup
      Properties:
         GroupDescription: Multus security group
         GroupName: multus
         VpcId: <MYVPC>

在接下来的部分,我们将在第一个子网中创建第一个 ENI:

   private1:
      Type: AWS::EC2::NetworkInterface
      Properties:
         Description: Interface 1 in az-a.
         Tags:
          - Key: multus
            Value: true
          - Key: Zone
            Value: <AZ-a>
          - Key:  node.k8s.amazonaws.com/no_manage
            Value: true
          - Key: cluster
            Value: <myclustername>
         SourceDestCheck: 'false'
         GroupSet:
         - !Ref multusSec
         SubnetId: <private subnet ID>

在下一部分,我们将在第二个子网中创建第二个 ENI:

   private2:
      Type: AWS::EC2::NetworkInterface
      Properties:
         Description: Interface 1 in az-b.
         Tags:
          - Key: multus
            Value: true
          - Key: Zone
            Value: <AZ-b>
          - Key:  node.k8s.amazonaws.com/no_manage
            Value: true
          - Key: cluster
            Value: <myclustername>
         SourceDestCheck: 'false'
         GroupSet:
         - !Ref multusSec
         SubnetId: <private subnet ID>

在最后一部分,我们将在第三个子网中创建最后一个 ENI:

   private3:
      Type: AWS::EC2::NetworkInterface
      Properties:
         Description: Interface 1 in az-c.
         Tags:
          - Key: multus
            Value: true
          - Key: Zone
            Value: <AZ-c>
          - Key:  node.k8s.amazonaws.com/no_manage
            Value: true
          - Key: cluster
            Value: <myclustername>
         SourceDestCheck: 'false'
         GroupSet:
         - !Ref multusSec
         SubnetId: <private subnet ID>

成功部署此脚本后,应该会创建三个新的接口;示例如下:

图 9.9 – Multus 的新 ENI

图 9.9 – Multus 的新 ENIs

将 ENI 附加到工作节点并配置 VPC 路由

您现在需要将接口附加到 EC2 实例使用的工作节点上。您可以通过控制台选择工作节点,然后选择操作 | 网络 | 附加网络接口,或者您也可以自动附加它们(稍后会详细介绍)。

由于我们仅在 AZ 中创建了一个接口,因此你将只看到与该实例所在 AZ 对应的接口。这将会在你的工作节点上创建一个新的以太网接口,并从私有子网中分配一个地址。你可以通过连接到主机并运行以下命令来查看配置:

$ ifconfig -a

这将输出类似下面的块,其中我们可以看到第二个 ENI(本例中的 eth1),但根据已分配给工作节点的接口数量,接口号可能会有所不同:

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
inet 192.168.27.63  netmask 255.255.224.0  broadcast 192.168.31.255
…..
eth1: flags=4098<BROADCAST,MULTICAST>  mtu 1500
ether 02:4f:d3:c2:fb:0e  txqueuelen 1000  (Ethernet)
……
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
inet 127.0.0.1  netmask 255.0.0.0
….

由于接口名称在每个工作节点上可能不同,我们需要对其进行规范化,以便使用 ip link 命令重命名它们。再次提醒,这可以通过以下命令按主机进行操作:

$ sudo ip link set eth1 name multus

到目前为止,这看起来可能有点繁琐,但我们仍然需要解决一个主要问题,那就是如何将流量路由到这个辅助接口以及从它路由回去。Multus 使用 NetworkAttachmentDefinition 来定义辅助接口使用的网络:

apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: ipvlan-private-a
spec:
  config: '{
      "cniVersion": "0.3.0",
      "type": "ipvlan",
      "master": "multus",
      "mode": "l3",
      "ipam": {
        "type": "host-local",
        "subnet": "192.168.96.0/19",
        "rangeStart": "192.168.96.20",
        "rangeEnd": "192.168.96.40",
        "gateway": "192.168.96.1"
      }
    }'

NetworkAttachmentDefinition 在 Pod 或 Deployment 中被引用;下文给出了一个示例。Pod 将通过 Multus 附加一个第二个接口,IP 地址在 NetworkAttachmentDefinition 文件的 config 部分中定义的 192.168.96.20192.168.96.40 之间。

重要提示

这里使用的范围只是将由 Multus 使用的子网 CIDR 的一个子集。

我们将使用 nodeSelector 标签,根据 NetworkAttachmentDefinition 中定义的 AZ 分配 IP 地址:

apiVersion: v1
kind: Pod
metadata:
  name: multus-pod
  annotations:
      k8s.v1.cni.cncf.io/networks: ipvlan-private-a
spec:
  nodeSelector:
    topology.kubernetes.io/zone: eu-central-1a
  containers:
        - name: busybox
          image: busybox
          imagePullPolicy: IfNotPresent
          command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']

如果你运行以下命令,你可以看到 Pod 被托管在 eu-central-1a AZ 中的工作节点上:

$ kubectl get pod -o=custom-columns=NODE:.spec.nodeName,NAME:.metadata.name

如果我们使用以下命令进入 Pod,你可以看到该 Pod 有两个接口:

$ kubectl exec --stdin --tty multus-pod  -- sh
# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
3: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 9001 qdisc noqueue
    link/ether 96:f4:8f:ef:82:92 brd ff:ff:ff:ff:ff:ff
    inet 192.168.14.60/32 scope global eth0
4: net1@if15: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:4f:d3:c2:fb:0e brd ff:ff:ff:ff:ff:ff
    inet 192.168.96.20/19 brd 192.168.127.255 scope global net1

现在进入问题的核心!VPC 是一个第三层网络构造,一切都需要 IP 地址才能通信,而 VPC 维护着路由表中所有 IP 地址/范围的路由。VPC 还维护着 MAC 地址映射,以将 IP 地址映射到正确的 ENI,确保流量指向正确的接口。

这一切都能正常工作,因为 IP 地址是从 VPC 请求的。但在 Multus 的情况下,它是分配 IP 地址的,而且由于它创建的是基于 ipvlan 的接口,因此 MAC 地址实际上是分配给 Multus 接口的(在 NetworkAttachmentDefinition 中是 eth2)。所以,VPC 并不知道 IP 地址已被分配,或者需要将它们映射到哪个 ENI。你需要使用以下命令将分配给 Pod 的 IP 地址与 Multus ENI 关联起来:

$ aws ec2 assign-private-ip-addresses --network-interface-id <multus ENI-ID> --private-ip-addresses 192.168.96.30

你可以通过几种方式来自动化其中的一些操作:

部署这些解决方案超出了本书的范围,但希望你能看到,VPC CNI 是在集群中实现原生 AWS 网络连接的最简单方式,无论是对于 IPv4 还是 IPv6。

如果你想做以下操作,仍然可能需要使用不同的 CNI:

  • 使用基于 IPsec、VXLAN 或 IP 内的覆盖网络

  • 支持增强型网络用例,如源 IP 地址保留、直接服务器返回和使用 eBPF 的低延迟网络

  • 支持 Windows 主机网络 服务 (HNS)

  • BGP 集成

使用另一种 CNI 的主要原因之一是 VPC 地址耗尽,这仍然是一个使用案例,但在第七章中讨论的前缀寻址,在 EKS 中的网络,解决了这个问题,只要你能将前缀分配给接口。如果你确实没有 VPC 地址,那么这仍然可能是使用另一种 CNI 的原因。

在这一部分,我们探讨了如何扩展和替换 VPC CNI。现在我们将回顾本章的关键学习点。

总结

我们首先描述了如何在新集群中配置 IPv6,以提供几乎无限的 IP 地址,并且不需要 NAT。我们还讨论了 IPv6 在通信方面的限制,以及如何使用主机本地插件、DNS64 和 NAT64 等技术来提供 IPv6 到 IPv4 的转换。

然后,我们探讨了如何使用 Calico 策略引擎通过提供 IPv4 L3/L4 网络策略(就像传统防火墙一样)来增强 EKS 的功能,这些策略可以用来限制 Pods 与外部 IP 地址之间的访问。

最后,我们探讨了 CNI 如何与插件和链式工作,并以 Multus 为例,展示了如何替换 AWS VPC CNI 及其带来的优势,但也可能增加的复杂性。我们还简要讨论了有一些有效的使用场景,需要使用不同的 CNI,但曾经是主要驱动因素的 VPC IP 耗尽问题,现在可以通过前缀地址来解决。

在下一章中,我们将查看升级集群的整体过程,并在之前章节讨论的一些概念基础上进行扩展。

进一步阅读

第十章:升级 EKS 集群

Kubernetes 社区每年大约发布三个新版本,并且会维护最近三个小版本的发布分支。此外,AWS 会在任何给定时刻维护至少四个生产就绪的 Kubernetes 版本;目前这些版本为 1.24、1.23、1.22 和 1.21。鉴于这两种不同的发布计划,您最终需要升级 EKS 集群,原因可能是您希望使用 Kubernetes 社区开发的新特性,或者因为 AWS 不再支持您正在使用的版本。好消息是,由于 EKS 是托管服务,AWS 会为您完成大部分升级工作!

本章将探讨最佳的操作方式以及它对正在运行的工作负载可能产生的影响。具体来说,我们将涵盖以下内容:

  • 升级 EKS 的原因及重点关注领域

  • 如何进行控制平面的就地升级

  • 升级节点及其关键组件

  • 创建新集群并迁移工作负载

技术要求

读者应对 YAML、基础网络和 EKS 架构有所了解。在开始本章之前,请确保您已具备以下内容:

  • 需要与您的 EKS API 端点进行网络连接

  • 您的工作站上已安装 AWS CLI 和 kubectl 二进制文件

  • 对 VPC 网络和如何创建 ENI 等网络对象有较好的理解

升级 EKS 的原因及重点关注领域

EKS 是一个社区项目,因此它不断发展;目前大版本大约每年发布三次,并且通常包含至少一个重大变更。例如,2021 年 4 月发布的 1.21 版本废弃了 Pod 安全策略,转而支持外部准入控制。这意味着您最终需要利用更新的 Kubernetes 特性。此外,Kubernetes 社区仅支持最近的三个小版本(例如 1.25、1.24 和 1.23),较旧版本通常会得到 1 年的补丁更新,之后您就需要自行处理了!

亚马逊会从上游 Kubernetes 版本发布中获取代码,进行测试并与 AWS 平台及组件(如 AWS VPC CNI 等)验证,然后将其打包并发布为 EKS 版本。这个过程大约需要 6 个月,通常支持 14 个月。以下图示说明了这一过程:

图 10.1 – 示例 Kubernetes 发布计划

图 10.1 – 示例 Kubernetes 发布计划

12 个月后,AWS 会通过控制台和 AWS 健康仪表板通知客户,某个版本即将达到 生命周期结束EOL)日期——有时也称为 支持结束EOS)日期,并且在 EOS 日期后的 14 个月内,AWS 将通过渐进式部署过程自动将控制平面升级到最早支持的版本。

作为集群拥有者,你可以在 EOS 日期之前随时选择升级控制平面,但你始终需要负责升级工作节点、插件以及任何核心组件,如kube-proxy

升级时有一些关键领域需要考虑,因为你不能回滚版本。如果想回滚,必须重新部署一个先前版本的集群。关键考虑因素如下:

  • Kubernetes API 或关键功能是否已弃用,可能需要更改部署清单或进行 Kubernetes 组件升级,例如替换 kubelet?

  • 插件、第三方 DaemonSets 等是否需要升级以支持新版本?

  • 是否有需要设计的新功能,例如使用Open Policy AgentOPA)来替代 Pod 策略?

  • 是否有需要应用的安全补丁?

总体来说,除非工作负载与 Kubernetes 控制平面相关或交互,否则对运行中的工作负载几乎没有影响,但在修改生产环境之前,始终值得先阅读发布说明并在较低环境中进行升级。

现在我们已经讨论了为什么需要升级,让我们来讨论如何进行集群升级。

如何进行控制平面的就地升级

如果你什么都不做,AWS 最终会升级你的控制平面,但如前所述,这可能会影响其他组件。因此,采取主动的升级方法是最好的。升级控制平面实际上是一个单击操作,AWS 将逐步升级 API 和 etcd 服务器。在大多数情况下,这样做是可以的,但正如前一部分所讨论的,它可能会导致服务中断。

因此,推荐采用结构化方法。在以下示例中,负责eksctl配置文件的团队,或者可能是更详细的插件开发团队,如 Argo CD、Flux 等。在以下图示中,这由平台工程团队负责,但在较小的公司中,这可能是 DevOps 或站点可靠性工程SRE)团队或应用开发团队的责任:

图 10.2 – 结构化升级方法

图 10.2 – 结构化升级方法

一旦主 IaC 模板创建完成,开发团队可以利用该模板在较低环境(测试/预生产)中测试工作负载,最终在生产环境中使用。

假设你已经使用eksctl创建了集群,你可以通过一个简单的一行命令来升级控制平面。如果我们使用前一章中的 IPv4 集群,可以使用以下命令进行升级:

$ eksctl upgrade cluster myipv4cluster --version=1.20 --approve

重要提示

如果你省略了--approve关键字,eksctl将不会做任何更改。还值得注意的是,通常你不能直接从 1.19 这样的次要版本跳跃到 1.22,必须先升级到 1.20,然后是 1.21!

该过程可能需要最多 20 分钟,因此值得规划一个变更窗口,因为在升级过程中,Kubernetes API 访问可能会间歇性中断(现有工作负载不应受到影响)。一旦控制平面升级完成,您应升级工作节点以匹配集群版本,然后再进行下一个版本的升级(eksctl 强制要求此步骤)。接下来我们来看看如何执行这个升级。

升级节点及其关键组件

简化升级过程是使用托管节点组的关键原因之一。如果我们想手动升级活动集群中的单个工作节点,我们需要执行以下操作:

  1. 如果我们希望在升级过程中保持整体集群容量(能够运行活动 Pods 的工作节点总数),可以添加一台新工作节点来运行那些将从旧版 Kubernetes 代理(kubelet 等)运行的节点中逐出的 Pods。

  2. 从我们正在处理的节点中排出 Pods,并将它们从调度过程中移除,以确保不会分配新的 Pods。

  3. 升级操作系统二进制文件并应用必要的补丁。

  4. 更新和配置 Kubernetes 代理(kubelet 等)。

  5. 一旦升级后的节点注册并准备就绪,将其重新添加到调度程序中。

  6. 更新任何关键组件,如kube-proxycoreDNS等。

如果我们的节点组包含 10 个或 20 个节点,您将会看到这会变得非常麻烦。

现在我们来看看,在集群控制平面升级后,我们如何升级工作节点。

升级托管节点组

升级集群后,如果查看该集群的托管节点组,您将看到立即更新链接。可以使用此链接,利用第八章中描述的自动扩展启动模板过程自动升级节点组,EKS 中的工作节点管理。下面显示了示例:

图 10.3 – 节点组更新

图 10.3 – 节点组更新

使用该链接将自动替换节点组中的所有 EC2 工作节点;您将看到一个弹出窗口(下面会显示示例),提供更多选项:

图 10.4 – 节点组更新策略

图 10.4 – 节点组更新策略

前面显示的升级节点对话框允许您执行以下操作:

  • 通过设置更新节点组 版本切换,用最新的 AMI 替换节点。

  • 使用更改启动模板 版本切换,替换当前的自动扩展启动模板为另一个模板。

  • 使用滚动更新策略(默认)从运行节点中排空 Pods。此策略会遵循定义的任何 Pod 中断预算,因此如果 Pods 无法平滑排空,更新将失败。另一种选择是强制更新策略,它将按滚动更新尝试排空 Pods,但如果失败,将简单地终止节点而不是使更新失败。

点击 eksctl

$ eksctl upgrade nodegroup   --name=ipv4mng   --cluster=myipv 4cluster  --kubernetes-version=1.20

你可以使用以下命令查看替换的状态。在以下示例中,可以看到较旧的 1.19 AMI 已设置为 SchedulingDisabled

$ kubectl get nodes --watch
NAME       STATUS       ROLES    AGE   VERSION
ipx.eu-central-1.compute.internal   Ready                      <none>   15m   v1.20.15-eks-ba74326
ip-192-168-40-12.eu-central-1.compute.internal    Ready,SchedulingDisabled   <none>   26d   v1.19.15-eks-9c63c4
ipy.eu-central-1.compute.internal    Ready                      <none>   15m   v1.20.15-eks-ba74326
ipz.eu-central-1.compute.internal   Ready                      <none>   15m   v1.20.15-eks-ba74326

升级自管节点组

升级自管节点将取决于你如何进行升级(先排空 Pods、替换节点或就地升级),以及安装了哪些额外组件。

通常,节点组应视为不可变的;因此,作为自动伸缩组的一部分的非托管节点组,您可以更改 AMI 并使用新的启动模板来强制替换,但作为操作员,你将负责将节点从调度器中移除(SchedulingDisabled),排空 Pods,然后进行扩展和收缩(实际上,所有这些都是托管组为你完成的)。

更简单的方法可能是直接创建一个新的节点组,将 Pods 移动到新的节点组,并删除旧的节点组。

更新核心组件

节点组将拥有更新后的 kubelet 代理,但像 kube-proxycoreDNSvpc-cni 等关键组件通常需要升级才能与特定的 Kubernetes 版本配合使用。

如果你使用以下命令查看升级后的集群和节点组中当前版本的 kube-proxy,你会看到它仍然是之前集群的版本(v1.19.16):

$ kubectl get daemonset kube-proxy --namespace kube-system -o=jsonpath='{$.spec.template.spec.containers[:1].image}'
1122334.dkr.ecr.eu-central-1.amazonaws.com/eks/kube-proxy:v1.19.16-eksbuild.2

我们可以使用 eksctl 或其他 IaC 工具进行升级。下一个示例展示了如何使用 eksctl utils 更新 kube-proxy

$ eksctl utils update-kube-proxy --cluster=myipv4cluster –approve
2022-09-11 10:27:07 "kube-proxy" is now up-to-date
$ kubectl get daemonset kube-proxy --namespace kube-system -o=jsonpath='{$.spec.template.spec.containers[:1].image}'
1122334.dkr.ecr.eu-central-1.amazonaws.com/eks/kube-proxy:v1.20.15-eksbuild.2

为简化此过程,AWS 引入了 EKS 附加组件,允许更新如 kube-proxy 等操作软件或监控守护进程,例如AWS Distro for OpenTelemetry (ADOT)。

如果我们使用 AWS 控制台并点击 vpc-cni 附加组件来升级实现 CNI 的 aws-node DaemonSet:

图 10.5 – 集群附加组件

图 10.5 – 集群附加组件

你也可以使用你选择的 IaC 工具以编程方式执行此操作。下一个示例展示了如何使用 eksctl 执行相同操作:

$ eksctl update addon --name vpc-cni --cluster myipv4cluster --version 1.11.3 --force
2022-09-11 10:47:40 []  Kubernetes version "1.20" in use by cluster "myipv4cluster"
2022-09-11 10:47:41 []  new version provided v1.11.3-eksbuild.1
2022-09-11 10:47:41 []  updating addon
2022-09-11 10:50:50 []  addon "vpc-cni" active

重要提示

使用 --force 会强制配置应用到集群。这些操作应该在低环境中进行测试,以确保在生产环境中执行时不会导致停机。

让我们看一下使用替代集群和/或节点组,在集群级别提供蓝绿部署方法。

创建新集群并迁移工作负载

如你所见,典型的升级通常涉及至少三个步骤:

  1. 升级控制平面

  2. 升级/替换工作节点,使用更新的 AMI 和 kubelet

  3. 至少升级核心组件,kube-proxycoreDNSvpc-cni

在这种方法中,Pod 必须首先被排空,并重新分配到工作节点,因为它们将被替换。如果管理不当,这可能会导致中断。另一种选择是部署新集群,然后迁移工作负载;这种方法有时被称为蓝绿集群部署。

重要提示

这是成本效益最差的方法,因为你将需要为两个控制平面付费,但如果你希望尽量减少中断,这种方法可能适合。我们在本书中只会在高层次上讨论这种方法,因为最常见的方法是升级 EKS 控制平面,然后使用托管工作节点升级工作节点,这样可以大大降低成本和复杂性。

多集群解决方案提出了一些不同的挑战:如何迁移工作负载?集群上是否已应用任何手动更改?如何提供入口和出口连接性?如何管理状态?以下图示说明了这些挑战的解决方案:

图 10.6 – 多集群解决方案

图 10.6 – 多集群解决方案

现在,为了理解一些挑战,让我们看一下将工作负载迁移到两个集群之间的一个方法。

你如何迁移工作负载?

使用 kubectl 或 Helm 时,当前上下文定义了你将使用哪个集群。通过切换上下文,可以在任一集群上部署相同的清单。在 图 10.6 中,CI/CD 管道可以自动化在任一集群上部署服务。例如,集群 1(v1.19)正在运行 Service A集群 2 可以使用 v1.20 创建,这可以触发在 集群 2 上部署 Service A

你如何提供一致的入口和出口网络访问?

出口(Pod 发起外部连接)可以使用内部或外部的 VPC NAT 网关来隐藏两个集群的 Pod 的 IP 地址,避免 Pod IP 地址发生变化。

TargetGroupBinding 实例,它将复用为两个集群配置的现有 ALB 或 NLB 目标组,这些集群使用基础设施即代码(IaC)在 EKS 集群外配置。以下是一个示例,并引用了 testapp 服务,端口为 80

apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
  name: testappt
  namespace: mynamespace
spec:
  serviceRef:
    name: testapp
    port: 80
  targetGroupARN: arn:aws:elasticloadbalancing:eu-west-1:1122224:targetgroup/example/fc3409bc5e613beb

现在,让我们看看应用程序通常如何管理状态。

你如何管理状态?

你的应用程序可能需要在数据库或文件系统中维护状态。只要这些服务通过 AWS 服务(如 关系型数据库服务 (RDS) 或 弹性文件系统 (EFS))配置在集群外部,它们就可以被任一集群引用。

在这些解决方案到位后,你可以轻松地在集群之间切换。通过先部署到新集群,并确保服务已注册到 ELB,你可以使过渡几乎无缝;然而,你将为这种配置支付更多费用。

在本节中,我们探讨了如何升级关键的 EKS 组件以及托管和非托管节点组所需的不同方法。现在我们将回顾本章的关键学习点。

总结

在本章中,我们探讨了为什么需要升级集群:要么是想使用新的 Kubernetes 特性,或者是修复一个 bug,或者是 AWS 正在弃用您使用的版本。我们确定了需要对每个集群执行三项操作:升级控制平面,升级工作节点,升级核心组件如 kube-proxycoreDNS

我们还讨论了控制平面升级相对简单,因为它是一个托管服务,但节点组和组件的升级可能更具挑战性。使用托管节点组和附加组件可以简化这一过程,但你也可以使用第二个集群,并在它们之间移动工作负载,升级非活动集群。这种方法——有时称为蓝绿集群部署——会增加成本和复杂性,因此不推荐使用,但它可以最小化因升级而导致的应用程序停机。

在下一章中,我们将探讨如何使用 AWS 弹性容器仓库ECR)作为应用和 Pods 的源。

进一步阅读

)

)

)

第三部分:在 EKS 上部署应用

本部分将涵盖与帮助您在 EKS 上部署应用相关的功能。本节包括如何将容器镜像存储在 Amazon ECR 上的完整指南,如何通过 AWS 存储服务(如 EBS 和 EFS)为应用提供持久卷,如何定义 Pod 安全性并通过 IAM 授予权限,以及如何暴露和负载均衡您的 Kubernetes 应用。在接下来的两章中,我们将探讨更高级的主题,例如如何使用 AWS Fargate,以及如何使用 App Mesh 来控制和监控我们的部署。

本节包含以下章节:

  • 第十一章构建应用并将其推送到 Amazon ECR

  • 第十二章使用 Amazon 存储部署 Pods

  • 第十三章使用 IAM 授予应用程序访问权限

  • 第十四章在 EKS 上为应用程序设置负载均衡

  • 第十五章与 AWS Fargate 一起工作

  • 第十六章与服务网格一起工作

第十一章:构建应用并将其推送到 Amazon ECR

一个 Kubernetes Pod 至少由一个容器组成。这些容器存储在公共或私有仓库中,并在工作节点接收到 Pod 规范并需要部署容器时被拉取。本章将介绍如何使用 AWS 弹性容器注册表ECR)安全地存储容器镜像,使用多个仓库,并允许 EKS 在部署 Pod 时使用这些镜像。

具体来说,我们将讨论以下主题:

  • 介绍 Amazon ECR

  • 理解仓库认证

  • 构建并将容器镜像推送到 ECR

  • 使用高级 ECR 功能

  • 在 EKS 集群中使用 ECR 镜像

技术要求

读者应该对 YAML、基础网络以及 EKS 架构有一定了解。在开始本章之前,请确保以下几点:

  • 你能够访问 EKS 集群 API 端点

  • 你的工作站上已安装 AWS CLI、Docker 和 kubectl 二进制文件

  • 你对 Docker 和 Dockerfile 有基本了解

介绍 Amazon ECR

第一章 中,我们讨论了容器的一般结构以及它如何使用联合文件系统来创建分层镜像。这个镜像格式已成为 开放容器倡议OCI)镜像规范,Podman 或 BuildKit 等各种开源构建工具都支持这一格式。

当你使用 docker build 命令构建镜像时,镜像会在本地创建,这对于本地机器来说是可以的,但当你需要在 EKS 或其他 Kubernetes 分发版/服务中使用该镜像时,你需要将其推送到一个仓库,以便其他系统能够访问,进而组成你的 EKS 集群。

如果你浏览 Docker Hub 并登录,你可以看到多个容器镜像,比如 Postgres、Redis、Python 等。每个镜像都会有一个版本标签,如 13.8,以及可能的最新标签,这通常(但不总是)表示容器镜像的最新版本。Docker Hub 是一个公共仓库,意味着可以从互联网上访问。它们被认为是公共仓库,任何拥有 Docker Hub ID 的人都可以访问。

ECR 托管多个仓库,这些仓库又托管多个版本的容器镜像(以及其他符合 OCI 标准的工件),就像 Docker Hub 一样,但访问控制通过 IAM 和仓库控制来管理,这些控制由你掌握,通常用于托管包含私有代码或配置的容器。

理解 ECR 最简单的方法是创建一个仓库。下面的示例展示了一个简单的 Terraform 配置,它将在当前 AWS 账户/区域中创建一个名为 myapp 的私有仓库:

resource "aws_ecr_repository" "myapp" {
  name                 = "myapp"
  image_tag_mutability = "MUTABLE"
  image_scanning_configuration {
    scan_on_push = true }}
output "repo-url" {
  value = aws_ecr_repository.myapp.repository_url}

ECR 配置中的两个关键属性是image_tag_mutability(允许使用现有标签的镜像上传,替换原有镜像)和scan_on_push,后者会在上传(推送)镜像后扫描该镜像的基本漏洞。

Terraform 代码将输出新创建的存储库 URL(例如,1122334.dkr.ecr.eu-central-1.amazonaws.com/myapp),我们稍后将使用该 URL 将本地镜像推送到 ECR。

ECR 的收费依据是存储在注册表中的镜像大小以及任何离开 AWS 的数据传输费用。例如,假设你存储了总共 60GB 的软件镜像,你将按每 GB $0.10 的价格收费,总计每月$6,但数据传入不收费。任何在同一地区的 EKS 集群拉取这些镜像时,不会收取数据传出费用。因此,总费用为每月$6。

实际上,您的费用可能会更低,因为在第一个年度对于私有存储库有免费层折扣,并且还包括一些免费的存储和传输出限额。有关更多信息,请参阅aws.amazon.com/ecr/pricing/

让我们看看 ECR 如何提供对存储库的安全访问。

理解存储库认证

正如我们所讨论的,ECR 存储库可以是私有的或公共的,您用来访问这些存储库的安全凭证将根据您创建的存储库类型而有所不同。

访问 ECR 私有存储库

对私有存储库的访问通过 AWS IAM 和存储库权限进行控制。如果您使用原生 AWS API,则可以使用 API 客户端(如 AWS CLI 或 Python 的boto3库)使用的标准签名版本 4 签名过程。

在本章中,我们将使用 Docker 命令与 ECR 存储库进行交互,因此我们需要将 AWS 的访问密钥和私密密钥转换为 Docker 能够理解的格式。这是通过aws ecr get-login-password命令实现的,并将输出传递给docker login命令。以下是一个示例:

$ aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 1122334.dkr.ecr.eu-central-1.amazonaws.com/myapp
WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json.
……
Login Succeeded

重要说明

请注意,凭证的有效期为 12 小时,过后需要重新运行docker login命令。

这意味着,像docker pulldocker push这样的 Docker 命令将带有一个认证令牌,允许它们与 ECR 进行交互。为了使用aws ecr get-login-password命令,所用的用户账户必须具有适当的 IAM 权限。

以下 IAM 策略是 EKS 工作节点用于访问和拉取镜像的默认策略,以及获取授权令牌(GetAuthorizationTokenget-login-password命令调用的底层 API)。

{ "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:BatchCheckLayerAvailability",
                "ecr:BatchGetImage",
                "ecr:GetDownloadUrlForLayer",
                "ecr:GetAuthorizationToken"
            ],
            "Resource": "*"}]}

除了 IAM 权限,每个注册表还可以应用单独的策略。通常,如前所示的 IAM 角色用于授予对 ECR 服务的广泛访问权限,而仓库策略则用于限制对特定仓库的访问。例如,以下 Terraform 资源会添加一个策略,允许账户22334455将镜像推送到我们的仓库:

resource "aws_ecr_repository_policy" "apppolicy" {
  repository = aws_ecr_repository.myapp.name
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowCrossAccountPush",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::22334455:root"
            },
            "Action": [
                "ecr:BatchCheckLayerAvailability",
                "ecr:CompleteLayerUpload",
                "ecr:InitiateLayerUpload",
                "ecr:PutImage",
                "ecr:UploadLayerPart"
            ]}]}
EOF
}

重要提示

还有一些可以应用的注册表级权限,这些权限用于限制访问复制和拉取缓存功能,后续章节会详细讨论这些内容。

另外,请注意aws_ecr_repository.myapp.name引用的是之前创建的仓库,如果你以不同的方式结构化你的 Terraform 仓库或代码,这个引用需要修改。

最后一条提示——私有和公共 ECR 仓库之间没有实际区别,它们的管理和计费方式相同。关键的区别在于,公共仓库允许匿名用户从中拉取镜像,并且在 Amazon ECR 公共画廊中可见。这意味着任何人都可以拉取镜像,而且由于仓库的费用基于数据传输,因此,匿名用户拉取你的镜像将会增加你的 整体账单

我们将仅为 EKS 使用私有 ECR 仓库,因此如果你需要进一步了解公共仓库,请参考docs.aws.amazon.com/AmazonECR/latest/public/what-is-ecr.html

现在我们已经理解了如何通过认证访问私有仓库,接下来让我们看看如何构建并将镜像推送到我们的 ECR 仓库。

构建并推送容器镜像到 ECR

如果我们考虑一个使用 Python 和 FastAPI 的简单 API,如下所示,首先需要将其打包成一个本地的 Docker 镜像。然后我们可以在本地测试它是否正常工作,再将其推送到 ECR。我选择 Python 和 FastAPI 是因为它们非常简单,可以快速启动,但你也可以使用任何语言或框架来创建容器。

以下是main.py文件中的 Python 代码:

#!/usr/bin/env python3
'''simple API server that returns Hello World'''
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
    return {"message": "Hello World"}

我们还需要一个requirements.txt文件,里面包含以下条目:

nyio==3.6.1
click==8.1.3
fastapi==0.83.0
h11==0.13.0
httptools==0.5.0
idna==3.3
importlib-metadata==4.12.0
pydantic==1.10.2
python-dotenv==0.21.0
PyYAML==6.0
sniffio==1.3.0
starlette==0.19.1
typing-extensions==4.3.0
uvicorn==0.18.3
uvloop==0.16.0
watchfiles==0.17.0
websockets==10.3
zipp==3.8.1

我们将使用一个简单的 Dockerfile(如下所示),它使用非 root 用户创建一个镜像,通过pip(在此情况下为 FastAPI 和 Uvicorn)安装必要的库,然后使用 Docker 的CMD关键字启动服务器:

FROM python:3.9
RUN pip install --upgrade pip
RUN adduser worker
USER worker
WORKDIR /home/worker
ENV PATH="/home/worker/.local/bin:${PATH}"
COPY ./requirements.txt /home/worker/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /home/worker/requirements.txt
COPY ./main.py /home/worker/main.py
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080", "--reload"]

然后,我们可以使用以下 Docker 命令来构建并运行容器:

$ docker build -t myapi:0.0.1 .
$ docker run -p 8080:8080 --rm myapi:0.0.1
……
INFO:     Uvicorn running on http://0.0.0.0:8080
INFO:     Application startup complete.

现在你可以通过http://127.0.0.1:8080发起 curl 请求获取回复,或者通过http://127.0.0.1:8080/docs获取 API 定义。现在我们有了一个可运行的应用程序,可以使用以下命令登录、标记并将镜像推送到我们在上一节中创建的 ECR 仓库:

$ aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 1122334.dkr.ecr.eu-central-1.amazonaws.com/myapp
WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json.
……
$ docker tag myapi:0.0.1 1122334.dkr.ecr.eu-central-1.amazonaws.com/myapp:0.0.1
$ docker images
REPOSITORY  TAG IMAGE ID       CREATED         SIZE
1122334.dkr.ecr.eu-central-1.amazonaws.com/myapp   0.0.1        c163cea7a037   9 hours ago     1.01GB
$ docker push 1122334.dkr.ecr.eu-central-1.amazonaws.com/myapp   :0.0.1
The push refers to repository [1122334.dkr.ecr.eu-central-1.amazonaws.com/myapp]
e6aadc5ffa3e: Pushed
……
54b354c15c5a: Pushed
0.0.1: digest: sha256:193687f5606a46e61634b1020edaea6e347281ea ba8ff41d328371982a533efc size: 3264

如果我们现在进入 AWS 控制台,我们将能够查看我们仓库中的镜像。由于我们启用了scan_on_push,我们还可以查看基本扫描检测到的任何漏洞。此扫描使用开源 Clair 项目来执行扫描:

图 11.1 – ECR 中初始镜像详情

图 11.1 – ECR 中初始镜像详情

如果我们点击右下角的详情链接,我们将看到问题的更详细视图,并附有指向常见漏洞和暴露CVE)编号的链接。接下来的示例引用了我镜像中发现的两个严重问题:

图 11.2 – 初始镜像扫描输出

图 11.2 – 初始镜像扫描输出

在我所工作的多数公司中,所有严重问题都需要在镜像被认为是安全之前修复。这可能是开发人员、DevOps 或平台工程师的责任,但实际上,确保镜像尽可能安全是每个人的责任。镜像修复可能是一个耗时的过程,但有一些简单的操作可以帮助你做到这一点!

通过简单地将 Dockerfile 中的基础镜像从python:3.9更改为python:3.10-slim-bullseye,并将其标记为版本 0.0.2,我已经去除了所有严重漏洞,减少了整体漏洞数,将镜像的大小减少了近四分之一,这将显著提高下载速度并节省成本。如下所示:

图 11.3 – 改进的容器安全态势和大小

图 11.3 – 改进的容器安全态势和大小

所以,我们已经将镜像上传到 ECR,严重漏洞已被修复,镜像大小也得到了优化。接下来,在演示如何在 EKS 中使用该镜像之前,让我们先看看 ECR 的一些更高级功能。

使用高级 ECR 功能

ECR 具有两个在管理大型 EKS 环境时非常有用的高级功能:拉取缓存,允许私有仓库缓存公共镜像,以及跨区域复制,将镜像复制到另一区域以供使用。让我们一起来探索这两个选项。

拉取缓存解释

拉取缓存允许私有仓库缓存来自公共 ECR 仓库或来自 Quay 的镜像(请注意,目前 Docker Hub 不被支持)。我们将在这个示例中使用公共 ECR 仓库,这样我们就可以在不向公共互联网开放工作节点的情况下提供公共镜像。

让我们使用以下 Terraform 代码在 ECR 中配置一个规则;请注意,它是在注册表级别配置的,而不是在仓库级别:

resource "aws_ecr_pull_through_cache_rule" "example" {
  ecr_repository_prefix = "ecr-public"
  upstream_registry_url = "public.ecr.aws"
}

一旦部署完成,我们可以登录并使用 ecr-public 前缀来拉取镜像。以下示例拉取最新的 Alpine 镜像:

$ aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 1122334.dkr.ecr.eu-central-1.amazonaws.com/ecr-public
WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json.
……
$ docker pull  1122334.dkr.ecr.eu-central-1.amazonaws.com/ecr-public/docker/library/alpine:latest
latest: Pulling from ecr-public/docker/library/alpine
Digest: sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395 ee852a8d9730fc2ad
Status: Downloaded newer image for 1122334.dkr.ecr.eu-central-1.amazonaws.com/ecr-public/docker/library/alpine:latest
1122334.dkr.ecr.eu-central-1.amazonaws.com/ecr-public/docker/library/alpine:latest

一个相应的私有仓库现在已创建(见下图),并启用了拉取缓存:

图 11.4 – 启用拉取缓存的仓库

图 11.4 – 启用拉取缓存的仓库

现在我们了解了如何在单一区域内使用仓库,让我们来看看如何在不同的 AWS 区域之间工作。

跨区域复制

你可能希望出于 灾难恢复DR)原因或全球覆盖的需求,在多个区域部署你的应用程序。你可以使用跨区域复制,将一个、多个或所有镜像从一个区域复制到另一个区域,或者多个区域。你可以在同一账户内操作,也可以跨不同账户进行操作,但请记住,如果你想从一个账户复制到另一个账户,你需要设置跨账户角色。

查看下图所示的 Terraform 配置,我们可以看到它由两部分组成。第一部分是一个 destination 规则,指定哪个区域和账户将作为复制的目标。请注意,您可以拥有多个目标规则。第二部分是可选的,指定一个 filter 规则,用于选择要复制的仓库。

在下图示例中,我们将使用 myapp 前缀。如果未使用此前缀,则所有镜像都会被复制:

data "aws_caller_identity" "current" {}
resource "aws_ecr_replication_configuration" "euwest1" {
  replication_configuration {
    rule {
      destination {
        region      = "eu-west-1"
        registry_id = data.aws_caller_identity.current.account_id
      }
      repository_filter {
        filter      = "myapp"
        filter_type = "PREFIX_MATCH"
      }
    }
  }
}

由于只有在配置了复制之后推送到仓库的内容才会被复制,现在我们需要推送一个新的标签,以便查看镜像是否成功复制到 eu-west-1

如果你遵循 构建并推送容器镜像到 ECR 部分中的命令,你可以为 myapp 仓库创建一个新镜像并将其推送到 ECR。

你将看到它被复制到该区域。如果你使用 AWS 控制台并进入相关的 repo/tag,你可以查看复制状态。在下图示例中,myapp:0.0.3 已成功复制到 eu-west-1

图 11.5 – 新镜像标签的复制状态

图 11.5 – 新镜像标签的复制状态

到此为止,我们已经探讨了 ECR 的功能和特性。让我们来看最后一部分,看看如何在 EKS 中使用 ECR 镜像。

在你的 EKS 集群中使用 ECR 镜像

EKS 工作节点可以从 ECR 拉取镜像,因为它们应该已应用 AmazonEC2ContainerRegistryReadOnly 管理角色。

因此,唯一需要做的就是在你的 Kubernetes 清单或 Helm 图表中指定完整的 <aws_account_id>.dkr.ecr.aws_region.amazonaws.com/<image-name>:<tag> ECR 路径。

基于 第四章在 EKS 上运行你的第一个应用程序,我们创建一个使用 myapp 容器的部署,并且创建一个暴露该服务到集群外的 NodePort 服务。唯一的实际区别是在 Pod 规范中,我们引用了完全限定的镜像名称。下面是示例。第一部分定义了 Kubernetes 部署:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  selector:
    matchLabels:
      app: fastapi
  replicas: 1
  template:
    metadata:
      labels:
        app: fastapi
    spec:
      containers:
      - name: fastapi
        image: "1122334.dkr.ecr.eu-central-1.amazonaws.com/myapp:0.0.2"

现在,我们定义服务:

---
apiVersion: v1
kind: Service
metadata:
 name: fastapi-dev
spec:
 type: NodePort
 selector:
   app: fastapi
 ports:
 - nodePort: 32410
   protocol: TCP
   port: 8080
   targetPort: 8080

由于这是一个 NodePort 服务,我们可以通过运行以下命令获取 Pod 所在主机的 IP 地址:

$ kubectl get pod <podname> -o jsonpath={.status.hostIP}

然后,我们可以使用curl http://<hostIP>:32410,并将看到 FastAPI 响应信息。

重要提示

请确保正确配置路由和安全组规则,以允许您的客户端通过端口32410连接到工作节点的 IP 地址。

在本节中,我们已经讨论了如何托管私有镜像,并将其最小化修改后部署到 EKS。接下来,我们将回顾本章的关键学习点。

总结

在本章中,我们探讨了如何使用 ECR 存储、缓存和复制容器镜像。使用 ECR 会产生费用,这些费用包括您仓库中所有镜像的总大小以及外出流量费用,但通过使用 ECR 的推送扫描功能,我们可以识别并解决关键依赖问题,同时优化镜像大小,支持更好的安全性和更具成本效益的镜像。

ECR 还有更多高级功能,允许我们支持灾难恢复(DR)策略或使用跨区域复制部署跨多个区域的应用程序,还可以从 ECR 公共仓库或 Quay 缓存公共镜像。最后,我们探讨了如何配置 IAM 和仓库策略来控制对镜像的访问,并将这些镜像拉入 EKS。

在下一章中,我们将探讨如何使用 AWS 存储驱动程序为 Pod 提供有状态存储,以作为应用程序和 Pod 的数据源。

进一步阅读

第十二章:使用 Amazon 存储部署 Pods

在大多数分布式或云原生应用程序中,一个指导原则是尽量减少状态的保存,因此到目前为止,我们已经部署了无状态的 Pods,这意味着当它们被销毁和重新创建时,它们所包含的任何数据都会丢失。在某些情况下,您可能希望在 Pod 中的容器之间共享数据、在重启/重新部署/崩溃之间保持状态或内容,或者在 Pods 之间共享数据,这时您就需要某种形式的持久化存储。

本章将探讨如何使用弹性块存储 (EBS)和弹性文件系统 (EFS)在多个容器、Pods 或部署之间保持容器的状态或内容。具体来说,我们将涵盖以下关键主题:

  • 了解 Kubernetes 卷、容器存储接口 (CSI)驱动程序以及 AWS 上的存储

  • 在您的集群中安装和配置 AWS CSI 驱动程序

  • 在应用程序中使用 EBS 卷

  • 在应用程序中使用 EFS 卷

技术要求

您应该熟悉 YAML、基本的网络知识和弹性 Kubernetes 服务 (EKS)架构。在开始本章之前,请确保您已具备以下条件:

  • 与 EKS 集群 API 端点的网络连接

  • 您的工作站上安装了 AWS 命令行接口 (CLI)、Docker 和 kubectl 二进制文件

  • 基本理解块存储和文件存储系统

了解 Kubernetes 卷、CSI 驱动程序以及 AWS 上的存储

Kubernetes 中的基本存储对象是,它表示一个目录(可能有数据,也可能没有数据),可以被 Pod 中的容器访问。您可以有临时卷,它们在容器重启时仍然存在,但与 Pod 的生命周期保持一致,并在 Pod 被销毁时被 Kubernetes 调度器销毁。持久卷则不会被 Kubernetes 销毁,它们与使用它们的 Pod 或 Pods 是独立存在的。

临时卷的最简单示例是emptyDir卷类型。下面是一个示例,它使用mountPath键将主机存储挂载到容器内部。由于两个容器使用相同的卷,它们即使挂载到不同的挂载点,也能看到相同的数据。当 Pod 死亡、崩溃或从节点中移除时,emptyDir卷中的数据会被删除并丢失:

apiVersion: v1
kind: Pod
metadata:
  name: empty-dir-example
spec:
  volumes:
  - name: shared-data
    emptyDir: {}
  containers:
  - name: nginx-container
    image: nginx
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
  - name: debian-container
    image: debian
    volumeMounts:
    - name: shared-data
      mountPath: /pod-data
    command: ["/bin/sh"]
    args: ["-c", "echo Hello from Debian > /pod-data/index.html"]

重要提示

在运行echo命令后,Pod 会崩溃,但这是预期的,所以请放心。

您还可以使用下面显示的hostPath类型创建一个持久化主机卷。在此示例中,创建一个卷并将其映射到主机的/data目录,该目录又通过mountPath键挂载到 nginx 容器中。这种配置与之前的emptyDir卷类型的主要区别在于,存储在卷中的任何数据都会在 Pod 被删除后仍保留在主机的/data目录中:

apiVersion: v1
kind: Pod
metadata:
  name: host-path-example
spec:
  containers:
  - image: nginx
    name: test
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: shared-data
  volumes:
  - name: shared-data
    hostPath:
            path: /data

这些存储卷的主要挑战是它们是主机特定的,因此,如果主机出现任何问题,或者 Pod 被调度到另一台主机上,数据将丢失或无法访问。

Kubernetes 最初支持更多的存储卷类型,例如awsElasticBlockStore,它使用外部/非主机的 AWS 资源,并去除了一些限制。这些支持外部存储卷类型的插件被称为in-tree插件,因为它们是由 Kubernetes 社区开发的。然而,支持卷配置变更和不同存储卷类型所需的工作量变得过于繁重,因此在 Kubernetes 1.13 版本中,CSI 被广泛引入并可供使用。

CSI 规范作为一种方式,能够一致地暴露基于块和文件的存储,无论存储类型或供应商如何。CSI 允许 AWS(以及其他供应商)为其存储服务(即 EBS 和 EFS)开发和支持存储驱动程序。

让我们更深入地了解 AWS 上的这些存储系统。

EBS

EBS 是基于块的存储,通常附加到单个弹性计算云EC2)实例或单个可用区AZ)中的 Pod 上。它有多种性能类型,从通用型(gp3 和 gp2)到高性能型(io1 和 io2),以及 SSD 和 HDD(磁性)类型。Amazon EBS 卷按千兆字节月GB-month)计费,即计算帐户中配置的 EBS 存储的千兆字节数以及使用时长。

重要说明

虽然 EBS 现在支持将最多 16 个基于 Nitro 的 EC2 实例连接到同一个 AZ 中的单个 EBS 卷,但这是一个相对较新的配置选项。

EFS

EFS 是基于 NFS(NFSv4.1 和 NFSv4.0)的文件存储,它允许多个 EC2 实例或 Pod 在多个 AZ 之间共享存储。EFS 提供的存储可以是区域性的(多 AZ)或仅跨单一 AZ,并支持标准存储和不常访问的数据模式。Amazon EFS 按每月使用的存储量计费,按 GB-月计量,还包括使用的存储类别以及存储在帐户中的使用时长。

选择 EBS 或 EFS 的标准因子有所不同,但通常,如果您需要一个可以跨多个 AZ 使用的共享存储解决方案,那么 EFS 是一个不错的选择。EBS 通常用于在单一 AZ 内提供高吞吐量的持久化卷。

让我们来看一下如何在集群中安装和配置 EBS 和 EFS 的不同 CSI 驱动程序。

在集群中安装和配置 AWS CSI 驱动程序

在本节中,我们将安装 EBS 和 EFS 驱动程序。两者的安装过程相似,详细步骤如下:

  1. 创建一个身份和访问管理IAM)策略,允许插件执行针对 EBS 或 EFS 的 AWS API 调用。

  2. 创建并将 IAM 角色映射到 EKS 服务账户(详细讨论请参见第一章)。

  3. 部署插件并配置它以使用在第 2 步中创建的服务帐户。

安装和配置 EBS CSI 驱动程序

驱动程序可以在github.com/kubernetes-sigs/aws-ebs-csi-driver找到。让我们开始安装它吧!

  1. 您可以从头开始创建 IAM 策略,或者使用AmazonEBSCSIDriverPolicy AWS 托管的策略。

  2. 我们现在可以创建角色了。我们将使用–role-only命令行选项,这样就不会创建 EKS 服务帐户。使用以下eksctl命令,根据需要调整命令行参数:

    $ eksctl create iamserviceaccount  --name ebs-csi-controller-sa --namespace kube-system --cluster myipv4cluster   --override-existing-serviceaccounts --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy  --approve --role-name AmazonEKS_EBS_CSI_DriverRole --role-only
    

重要提示

在前面的示例中,我们使用了在第九章中创建的集群。如果您使用的是不同的集群,则需要更改--cluster参数,以反映您的集群名称。

  1. 您可以使用以下eksctl命令为 EBS CSI 控制器创建一个附加组件,该命令将部署 CSI Pods,并创建访问 AWS API 所需的服务帐户,使用的是第 2 步中创建的角色:

    $ eksctl create addon --name aws-ebs-csi-driver --cluster myipv4cluster  --service-account-role-arn arn:aws:iam::11223344:role/AmazonEKS_EBS_CSI_DriverRole  --force
    2022-09-22 19:59:19 []  Kubernetes version "1.20" in use by cluster "myipv4cluster"
    ………
    2022-09-22 20:00:28 []  addon "aws-ebs-csi-driver" active
    
  2. 您可以使用以下命令验证控制器和 DaemonSets 是否已部署:

    $ kubectl get pods -n kube-system | grep ebs
    ebs-csi-controller-2233-p75xg   6/6     Running   1
    ebs-csi-controller-3444-rb9zg   6/6     Running   0
    ebs-csi-node-94pgc              3/3     Running   0
    ebs-csi-node-mwdqc              3/3     Running   0
    ebs-csi-node-t9h77              3/3     Running   0
    $ kubectl logs deployment/ebs-csi-controller -n kube-system -c csi-provisioner
    ………
    I0922 19:59:53.169651       1 leaderelection.go:258] successfully acquired lease kube-system/ebs-csi-aws-com
    ………
    

使用 EBS 卷与您的应用程序部分,我们将看到如何将 EBS 卷直接附加到 Pods,但在此之前,让我们先安装 EFS 驱动程序。

安装和配置 EFS CSI 驱动程序

驱动程序可以在github.com/kubernetes-sigs/aws-efs-csi-driver找到。让我们开始安装它吧!

  1. 您可以从头开始创建 IAM 策略,或者使用此处找到的示例策略:raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/docs/iam-policy-example.json。可以使用以下命令下载并创建 IAM 策略:

    $ curl -o iam-policy-example.json https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/docs/iam-policy-example.json
    $ aws iam create-policy --policy-name AmazonEKS_EFS_CSI_Driver_Policy --policy-document file://iam-policy-example.json
    
  2. 我们现在可以使用以下eksctl命令创建角色及相关 EKS 服务帐户,并根据需要调整命令行参数(您需要指定在第 1 步中创建的策略的Amazon 资源名称ARN),以及集群名称和区域)。最重要的是要验证新服务帐户是否有新 IAM 角色的注解:

    $ eksctl create iamserviceaccount  --cluster myipv4cluster     --namespace kube-system  --name efs-csi-controller-sa --attach-policy-arn arn:aws:iam::11223344:policy/AmazonEKS_EFS_CSI_Driver_Policy     --approve  --region eu-central-1
    2022-09-22 20:32:29 []  3 existing iamserviceaccount(s) (kube-system/ebs-csi-controller-sa,kube-system/eni-allocator,kube-system/multus) will be exclude
    ………
    022-09-22 20:32:59 []  created serviceaccount "kube-system/efs-csi-controller-sa"
    $ kubectl describe sa efs-csi-controller-sa -n kube-system
    Name:                efs-csi-controller-sa
    ……
    Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::076637564853:role/eksctl-myipv4cluster-addon-iamserviceaccount-Role1-P08589EN3NY7
    …..
    
  3. 我们将使用 Helm 安装这个 EFS CSI 驱动程序,因为与 EBS 驱动程序不同,截至写作时,EFS 驱动程序不支持作为附加组件。以下命令将把 EFS 仓库添加到 Helm,并部署 Helm 图表,重用在第 2 步中创建的 EKS 服务帐户:

重要提示

Image.repository是区域特定的,相关仓库可以在docs.aws.amazon.com/eks/latest/userguide/add-ons-images.html找到。

$ helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/
$ helm repo update
$ helm search repo aws-efs-csi-driver
NAME CHART VERSION   APP VERSION     DESCRIPTION
aws-efs-csi-driver/aws-efs-csi-driver   2.2.7 1.4.0 A Helm chart for AWS EFS CSI Driver
$ helm upgrade -i aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver --namespace kube-system --set image.repository=602401143452.dkr.ecr.eu-central-1.amazonaws.com/eks/aws-efs-csi-driver --set controller.serviceAccount.create=false --set controller.serviceAccount.name=efs-csi-controller-sa
$ kubectl get pod -n kube-system -l "app.kubernetes.io/name=aws-efs-csi-driver,app.kubernetes.io/instance=aws-efs-csi-driver"
NAME                 READY   STATUS    RESTARTS   AGE
efs-csi-controller-122-hrzfg          3/3     Running   0
efs-csi-controller-1234-q8wpt         3/3     Running   0
efs-csi-node-2g46k                    3/3     Running   0
efs-csi-node-59rsx                    3/3     Running   0
efs-csi-node-ncsk8                    3/3     Running   0
$ kubectl logs deployment/efs-csi-controller -n kube-system -c csi-provisioner
………
I0922 20:51:53.306805       1 leaderelection.go:253] successfully acquired lease kube-system/efs-csi-aws-com
……

重要提示

EFS 插件需要预先配置好的 EFS 集群可用;我们将在使用 EFS 卷与您的应用部分讨论如何创建该集群。

现在我们已经安装并运行了这两个驱动程序,我们可以查看它们如何被 Pod 使用来存储数据。

在应用程序中使用 EBS 卷

Kubernetes 有三种主要的kinds用于持久化存储。PersistentVolumePV)代表实际的存储,在附加的存储系统中,在本例中是 EBS 卷。其他组件包括StorageClassSC),它定义了存储的特性,以及PersistentVolumeClaimPVC),它代表对存储的请求,该请求由根据 SC 的 PV 来满足。

PVC 存在的原因是不同的 Pod 可能需要不同类型的存储,例如,多个 Pod 共享的存储或仅专门为一个 Pod 提供的存储。PVC 提供了一种抽象,它位于开发人员或 DevOps 工程师为其应用所需的存储和集群管理员提供的存储类型之间。

下图说明了 EBS 卷、PV、PVC 和 Pod 之间的关系:

图 12.1 – EBS 卷

图 12.1 – EBS 卷

重要的是要知道,EBS 卷是特定于区域(Region)和可用区(AZ)的;你不能在不同的可用区之间移动 EBS 卷。相反,你需要创建一个快照,然后在新的可用区创建一个新卷。PV(EBS 卷)可以由 AWS 管理员静态创建,也可以在你消费 PVC 时动态创建,但它只能被与卷本身位于同一可用区的工作节点消费。

我们将重点介绍卷的动态创建方法,因为这是最简单的实现方式。最新的 EBS CSI 驱动程序会自动创建一个gp2存储类,可以使用以下命令查看它:

$ kubectl get storageclass
NAME  PROVISIONER  RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  50d

我们想要使用gp3,它是 AWS 上成本效益更高且性能更好的存储类型,因此让我们使用以下清单创建一个新的存储类,并通过$ kubectl create -f SC-config.yaml命令将其部署:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: gp3
provisioner: ebs.csi.aws.com # Amazon EBS CSI driver
parameters:
  type: gp3
  encrypted: 'true'
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete

现在我们可以创建一个 PVC,利用新的存储类(SC)。由于我们使用的是动态配置,我们不需要创建 PV,因为它将在我们部署一个引用新 PVC 的 Pod 时自动创建。

以下清单将创建 PVC,并可以通过$ kubectl create -f VC-config.yaml命令进行部署。该清单包含我们将使用的存储类(在本例中为gp3)以及要创建的卷的大小(4 Gi)。我们没有在 PVC 中指定任何加密要求,但由于这在存储类中已设置,因此卷将被加密;如果我们希望允许开发人员选择未加密的卷,我们也可以创建一个不加密的gp3存储类:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: gp3
  resources:
    requests:
      storage: 4Gi

accessModes定义了卷的挂载方式,以下是这些方式;但是,EBS 仅支持ReadWriteOnce

  • ReadWriteOnce(RWO):此卷只能由单个节点以读写方式挂载。

  • ReadOnlyMany(ROX):此卷可以被多个节点以只读方式挂载。

  • ReadWriteMany(RWX):此卷可以被多个节点以读写方式挂载。

  • ReadWriteOncePod(RWOP):此卷可以被单个 Pod 以读写方式挂载。

以下命令展示了 PVC 处于挂起状态(因为没有 Pod 对其进行声明),并且没有创建关联的 EBS 卷(PV),因为 PVC 仍处于挂起状态:

$ kubectl create -f ebs-pvc.yaml
persistentvolumeclaim/ebs-claim created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITYACCESS MODES STORAGECLASS   AGE
ebs-claim   Pending          gp3            10s
$ kubectl get pv
No resources found

我们现在可以部署一个使用该 PVC 的 Pod,进而,利用 EBS CSI 驱动程序创建一个新的 EBS 卷(动态配置),并根据 Pod 规格中的 mountPath 将其挂载到 Pod 上。

重要说明

值得注意的是,由于需要先创建 EBS 卷,因此 Pod 部署时间会更长。如果需要更快的启动时间,则可以使用静态配置,在 Pod 部署前先创建 PV。

以下清单将创建 Pod,并引用之前创建的 PVC。可以使用 $ kubectl create -f ebs-pod.yaml 命令进行部署:

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: debian
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: ebs-claim

我们使用以下命令来验证 Pod 的成功部署和 EBS 卷的创建。一旦 Pod 被创建,我们可以看到 PVC 现在处于 Bound 状态,并且一个新的 PV 也在 Bound 状态:

$ kubectl create -f ebs-pod.yaml
pod/app created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
ebs-claim   Bound    pvc-18661cce-cda5-4779-86b4-21cb76a5ecc0   4Gi        RWO            gp3            30m
$ kubectl get pv
NAME CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM
pvc-1 4Gi RWO Delete  Bound    default/ebs-claim   gp3     17s

如果我们详细查看 PV,可以看到通过查看 VolumeHandle 键来查看 AWS 中创建的卷 ID:

$ kubectl describe pv pvc-1
Name:              pvc-1
Labels:            <none>
…
StorageClass:      gp3
Status:            Bound
Claim:             default/ebs-claim
Reclaim Policy:    Delete
Access Modes:      RWO
VolumeMode:        Filesystem
Capacity:          4Gi
Node Affinity:
  Required Terms:
    Term 0: topology.ebs.csi.aws.com/zone in [eu-central-1b]
Message:
Source:
    Type:  CSI ( (CSI) volume source)
    Driver:            ebs.csi.aws.com
    FSType:            ext4
    VolumeHandle:      vol-00f7c5d865ef5c14f
    ReadOnly:          false
    …

当 PVC 被移除时,回收策略(默认为动态配置中 SC 定义的值)决定了发生什么。在前面的示例中,回收策略Delete,这意味着 Kubernetes 资源(PV 和 PVC)将被删除,同时也会删除关联的 EBS 卷。如果你希望保留 EBS 卷,则应在 SC 中设置 Retain 值。

现在,进入 AWS 控制台并搜索卷 ID。下面的示例展示了卷、已配置的大小和类型,以及 密钥管理服务KMS)的详细信息和吞吐量:

图 12.2 – AWS 控制台中的 EBS 卷

图 12.2 – AWS 控制台中的 EBS 卷

现在我们已经通过 EBS 设置了基于块的存储,让我们来看一下如何使用 EFS 实现多个 Pod 共享的基于文件系统的存储。

使用 EFS 卷与应用程序

EFS 是一个共享存储平台,不同于 EBS,因此在 Kubernetes 层面,虽然你有相同的对象,SC、PV 和 PVC,但存储的访问方式以及存储的创建方式是完全不同的。

下图展示了 EFS 实例/卷与 Kubernetes 的 PV、PVC 和 Pod 之间的关系:

图 12.3 – EFS 卷

图 12.3 – EFS 卷

尽管我们已经安装了 CSI 驱动程序,但没有 EFS 实例和在所需子网中的挂载目标,我们无法配置卷。接下来让我们看看如何创建它们。

创建 EFS 实例和挂载目标

你可以通过多种方式完成此操作,但我们将使用 AWS CLI。首先创建 EFS 文件系统并获取文件系统 ID。以下命令将创建 EFS 实例,并筛选响应仅返回FileSystemId。请根据你使用的区域调整–region参数:

$ aws efs create-file-system --region eu-central-1  --performance-mode generalPurpose --query 'FileSystemId' --output text
fs-078166286587fc22

下一步是确定我们希望用于挂载目标的子网。理想情况下,我们将挂载目标放置在与工作节点相同的子网中。以下命令将列出给定虚拟私有云VPC)的所有子网(你需要提供正确的 VPC-ID),并列出托管节点组正在使用的子网和安全组:

$ aws ec2 describe-subnets  --filters "Name=vpc-id,Values=vpc-123" --query 'Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}'  --output table
+------------------+--------------------+-----------------+
| AvailabilityZone |     CidrBlock      |   SubnetId         |
+------------------+--------------------+-----------------+
|  eu-central-1a   |  192.168.96.0/19   |  subnet-1  |
|  eu-central-1b   |  192.168.32.0/19   |  subnet-2  |
|  eu-central-1a   |  192.168.0.0/19    |  subnet-3  |
|  eu-central-1c   |  192.168.160.0/19  |  subnet-4  |
|  eu-central-1c   |  192.168.64.0/19   |  subnet-5  |
+------------------+--------------------+-----------------+
$ aws ec2 describe-instances --filters "Name=tag:aws:eks:cluster-name,Values=myipv4cluster"   --query "Reservations[*].Instances[*].{Instance:InstanceId,Subnet:SubnetId,PrivateIP:PrivateIpAddress}"  --output table
+---------------------+------------------+----------------+
|      Instance       |    PrivateIP     |          Subnet   |
+---------------------+------------------+-------- -------+
|  i-01437f1b219217d8a|  192.168.12.212  |  subnet-3  |
|  i-0f74d4d5b7e5dc146|  192.168.70.114  |  subnet-5  |
|  i-04c48cc4d2ac11ca6|  192.168.63.61   |  subnet-2  |
+---------------------+------------------+----------------+

从之前的输出中我们可以看到,我们应该在子网 3、5 和 2 中创建挂载点,因为这些子网中包含属于myipv4cluster的工作节点。

重要提示

这些子网也覆盖了三个 AZ,以确保高可用性。

我们现在可以使用下一个命令识别这些实例使用的安全组。在我们的例子中,所有实例都属于同一个安全组,因为它们属于同一个托管节点组。为了简便,我们将使用这个安全组作为 EFS 挂载目标,但你可能希望为 EFS 创建一个单独的安全组。然而,确保你使用的任何安全组都允许TCP/2049端口在 Pods 和 EFS 挂载目标之间通信:

$ aws ec2 describe-instances --filters "Name=tag:aws:eks:cluster-name,Values=myipv4cluster"    --query "Reservations[*].Instances[*].SecurityGroups[*]"  --output table
|GroupId  |                GroupName                 |
+-----------------------+--------------------------------+
|  sg-123 |  eks-cluster-sg-myipv4cluster-940370103  |
|  sg-123 |  eks-cluster-sg-myipv4cluster-940370103  |
|  sg-123 |  eks-cluster-sg-myipv4cluster-940370103  |
+-----------------------+---------------------------------+

我们现在可以使用以下命令创建并验证挂载点,每个子网/AZ 一个。验证挂载目标时,你将看到分配给弹性网络接口ENI)的 IP 地址被放置在子网中,并将由 Pods 使用:

$ aws efs create-mount-target --file-system-id fs-078166286587fc22--security-groups sg-123 --subnet-id subnet-3
<repeat for remaining subnets>
$ aws efs describe-mount-targets --file-system-id fs-078166286587fc22 --query "MountTargets[*].{id:MountTargetId,az:AvailabilityZoneName,subnet:SubnetId,EFSIP:IpAddress}" --output
+----------------+----------------+-----------+-----------+
| EFSIP          |      az        |     id    | subnet     |
+----------------+----------------+-----------------------+
|  192.168.10.59 |  eu-central-1a |  fsmt-22  |  subnet-3  |
|  192.168.66.201|  eu-central-1c |  fsmt-33  |  subnet-4  |
|  192.168.34.140|  eu-central-1b |  fsmt-44  |  subnet-5  |
+----------------+----------------+-----------+-----------+

我们现在已经设置好 EFS 并将其提供给 Pods;接下来的步骤几乎与 EBS 相同,包括设置 Kubernetes 对象以使用 EFS。

创建你的 EFS 集群对象

我们需要创建 SC 和示例清单,如以下代码片段所示。你需要替换fileSystemId键,然后使用$ kubectl create -f SC-config.yaml命令进行部署:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
parameters:
  provisioningMode: efs-ap
  fileSystemId: fs-078166286587fc22
  directoryPerms: "700"

我们现在可以使用以下清单创建消耗 SC 的 PVC,然后使用$ kubectl create -f pvc.yaml命令进行部署。请注意,accessMode现在设置为ReadWriteMany,因为这是共享存储:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi

如果我们检查使用下列命令创建的 PVC 和 PV,可以看到新的 PVC 和 PV 被创建并绑定,因为我们使用的是动态供给。这与 EBS 不同,EBS 只有在 PVC 被使用时才会创建 PV。而对于 EFS,只有在使用时才会收费,和 EBS 卷不同,EBS 卷在创建时就开始收费,因此在 PVC 创建后立即创建 PVC/PV 组合不会有任何问题:

$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES   STORAGECLASS   AGE
ebs-claim Bound  pvc-1   4Gi    RWO     gp3            14h
efs-claim Bound  pvc-2   5Gi   RWX      efs-sc         4s
$kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM STATUS CLAIM SC REASON AGE
pvc-1 4Gi RWO Delete Bound default/ebs-claim gp3 16h
pvc-2 5Gi RWX Delete Bound default/efs-claim efs-sc  2m1s

如果查看控制器日志(以下展示了一个示例),你可以看到 CSI 驱动程序发起了创建卷的调用:

$ kubectl logs efs-csi-controller-xx -n kube-system  -c csi-provisioner   --tail 10
………
I1005 10:55:45.301869   1 event.go:282] Event (v1.ObjectReference {Kind:"PersistentVolumeClaim", Namespace:"default", Name:"efs-claim", UID:"323", APIVersion:"v1", ResourceVersion:"15905560", FieldPath:""}): type: 'Normal' reason: 'Provisioning' External provisioner is provisioning volume for claim "default/efs-claim"
I1005 10:55:46.515609       1 controller.go:838] successfully created PV pvc-2 for PVC efs-claim and csi volume name fs-078166286587fc22::fsap-013a6156108263624

最后一步是为 Pod 配置使用 PVC,并将 EFS 卷挂载到容器内的挂载点。下一个清单将创建一个基于 CentOS 的单一容器,并将卷挂载到/data目录下,可以通过$ kubectl create -f pod2.yaml命令进行部署:

apiVersion: v1
kind: Pod
metadata:
  name: efs-app
spec:
  containers:
    - name: app
      image: centos
      command: ["/bin/sh"]
      args: ["-c", "while true; do echo $(date -u) >> /data/out; sleep 5; done"]
      volumeMounts:
        - name: persistent-storage
          mountPath: /data
  volumes:
    - name: persistent-storage
      persistentVolumeClaim:
        claimName: efs-claim

您可以使用以下命令验证数据是否正在生成并存储在 EFS 中。如果您删除并重新创建 Pod,先前 Pod 的数据将被保留:

$ kubectl exec -it efs-app -- tail /data/out
Wed Oct 5 20:56:03 UTC 2022
Wed Oct 5 20:56:08 UTC 2022

由于Reclaim Policy的默认设置为Delete,如果删除 PVC,您将删除 PV 及其对应的 EFS 数据。总而言之,在本节中,我们探讨了如何安装和配置 EBS 和 EFS CSI 驱动程序,以及如何使用它们为 Pod 创建持久化存储。接下来,我们将回顾本章的关键学习点。

总结

本章中,我们探讨了 EBS(块存储)与 EFS(文件系统存储)的区别。我们发现,EBS 通常用于需要为每个 Pod 提供专用卷的场景,它的大小是固定的,并且一旦创建就开始收费。而 EFS 是共享存储,可以跨多个 Pods 挂载,按需扩展,且只对实际使用的部分收费。

我们还讨论了 EFS 相比 EBS 需要更多的配置,因为在 EKS 中使用 EFS 之前,需要先部署 EBS 文件系统和挂载目标。EFS 作为共享存储平台,设置起来较为复杂,而 EBS 只是为单个节点提供的网络附加存储。EBS 通常更便宜,但它主要用于附加到单个实例(EC2)的卷。

接着,我们回顾了如何安装 CSI 驱动程序,创建 EBS CSI 驱动程序的附加组件,并为 EFS CSI 驱动程序使用 Helm。驱动程序安装完成后,我们探讨了 Kubernetes 对象(SC、PVC 和 PV)及如何通过动态供应在 Kubernetes 中创建 EBS 和 EFS 卷,而不是让管理员为我们预先创建这些卷。

在下一章中,我们将讨论如何为您的应用程序/Pods 授予 IAM 权限,使其能够使用 AWS 服务。

深入阅读

第十三章:使用 IAM 授予应用程序访问权限。

AWS 提供超过 200 种服务,从 SQL/NoSQL 数据库到机器学习和量子计算。很可能在某个时候,您希望从部署在 EKS 上的应用程序中使用这些服务之一。

本章将探讨如何向 Pods 授予 IAM 权限,如何在应用程序中使用相关凭证连接到 AWS 服务,并如何解决整个过程中的问题。具体来说,我们将涵盖以下内容:

  • 理解 IAM Service Account 角色IRSA)是什么及其解决了什么问题。

  • 在应用程序中使用 IRSA。

  • 如何在 EKS 上解决 IAM 问题。

技术要求。

读者应熟悉 YAML、AWS IAM 和 EKS 架构。在开始本章之前,请确保以下内容:

  • 您可以连接到您的 EKS 集群 API 终端。

  • AWS CLI、Docker 和 kubectl 二进制文件已安装在您的工作站上。

  • 您对 AWS IAM 有基本的了解。

理解 IRSA。

首先,让我们看看标准 EC2 实例的 IAM 角色分配工作原理。在 AWS IAM 中,角色用于分配权限(使用一个或多个策略)。可以使用实例配置文件将角色分配给 EC2 实例,该配置文件只是附加到特定 EC2 实例的 IAM 角色的容器。

图 13.1 – EC2 角色分配

图 13.1 – EC2 角色分配。

当创建一个 EC2 实例并分配角色时,AWS 平台将自动创建一个实例配置文件。当该实例启动时,它将向 169.254.169.254 发送网络调用,并查询该实例分配了哪个(如果有)实例配置文件(或角色)。如果已分配一个角色,则可以检索访问凭证,下面是一个示例。这些凭证包括访问密钥和秘密密钥,用于所有 AWS API 调用,并标识角色及其授予的权限:

{ "Code" : "Success",
  "LastUpdated" : "2022-04-26T16:39:16Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIAIOSFODNN7EXAMPLE",
  "SecretAccessKey" : "bPxRfiCYEXAMPLEKEY",
  "Token" : "token",
  "Expiration" : "2022-05-17T15:09:54Z"}

如果登录到 EC2 实例,可以使用下面显示的 aws sts 命令查看附加到运行实例的实例配置文件:

$ aws sts get-caller-identity
{
 "UserId": "hdghd78898:i-014",
 "Account": "11223344,
 "Arn": "arn:aws:sts::11223344:assumed-role/<IPROFILE>/i-014"
}

AWS 引入了 IMDSv2,以在容器平台上提供一些安全控制,让我们来审视主要变更。

引入 IMDSv2。

正如我们所讨论的,实例配置文件信息是通过 EC2 IMDS 检索的,该服务在每个 VPC 中的地址为 169.254.169.254。此服务对于 EC2 实例的操作至关重要,不应阻止工作节点使用它,但应限制 Pods 使用。原始 IMDS 版本,即版本 1,不允许对谁可以使用它施加任何限制。

IMDSv2 是对 IMDSv1 的增强,使用面向会话的请求为服务增加安全控制。IMDSv2 返回一个令牌,用于请求元数据和凭证。由于令牌从不存储在 IMDSv2 中,因此当使用该令牌的进程或应用程序结束时,令牌就会丢失。元数据服务使用 TTL 跳数在 IPv4/IPv6 数据包中允许请求。默认情况下,它设置为 1,意味着请求只能直接来自 EC2。运行在主机上的任何 Pod 都会将跳数增加到 2(因为它们在主机内使用桥接网络),这会阻止它们直接使用 IMDSv2 服务并获取主机实例配置凭证。

为强制使用 IMDSv2 并限制跳数,你可以在所有工作节点上运行以下命令,或者将配置添加到用于工作节点的启动模板的基础设施即代码IaC)定义中:

$ aws ec2 modify-instance-metadata-options --instance-id i-1122233 --http-tokens required --http-put-response-hop-limit 1
{
    "InstanceId": "i-112233",
    "InstanceMetadataOptions": {
        "State": "pending",
        "HttpEndpoint": "enabled",
        "HttpTokens": "required",
        "HttpPutResponseHopLimit": 1
    }
}

然而,这只有在 Pod 使用 IMDSv2 时才有效。在大多数情况下,Pod 使用 AWS SDK 来调用 AWS API,这默认使用 IMDSv2 来获取凭证。这些凭证将应用跳数限制。然而,如果 Pod 使用 IMDSv1,它仍然可以获取主机凭证。在这种情况下,最佳实践是仅给工作节点最小的权限(例如,仅允许拉取 ECR 容器),或者使用 Calico 网络策略或iptables规则限制对 IMDSv1 的访问。

现在,我们已经了解了实例和 Pod 如何使用 IMDS 来获取分配给主机的凭证(以及如何限制它们这样做)。接下来,让我们看看如何使用 IRSA 将特定的角色直接分配给 Pods。

IRSA 的工作原理

IRSA 允许你通过将 Kubernetes 的AssumeRoleWithWebIdentity API 调用与 AWS 安全令牌服务STS)关联,从而为特定的 Pod 分配特定权限,STS 将把 Kubernetes 生成的凭证交换为 AWS IAM 生成的凭证。它通过将特定 EKS 集群的 OIDC 提供程序作为主体,并假设在 SA 注释中定义的角色来实现这一点(步骤3)。

图 13.2 - IRSA 的工作原理

图 13.2 - IRSA 的工作原理

现在我们大致了解了 IRSA 的工作原理,接下来让我们看看需要配置什么,才能让 Pod 使用 IRSA。

在应用程序中使用 IRSA

现在你已经了解了 IRSA 的基本概念,接下来让我们看看如何在应用程序中配置和使用它。我们将查看如何手动部署 Pod 并配置它以使用 IRSA,然后我们将看看如何通过eksctl简化整个过程。

如何部署 Pod 并使用 IRSA 凭证

第一步是确保已为集群配置了 OIDC 提供程序。如果你使用了eksctl,则此配置已经完成:

$ aws eks describe-cluster --name myipv4cluster --query "cluster.identity.oidc.issuer" --output text
https://oidc.eks.eu-central-1.amazonaws.com/id/763683678

如果你还没有启用它,可以使用以下eksctl命令:

$ eksctl utils associate-iam-oidc-provider --cluster cluster_name –approve

既然我们已经为集群创建了身份,并且可以使用它,在 IAM 中,我们可以创建相关的策略和角色。假设我们希望将 Pod 的访问权限授予账户中的所有 S3 存储桶和对象。那么,我们将使用以下策略来提供 S3 访问:

{ "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetAccountPublicAccessBlock",
                "s3:GetBucketAcl",
                "s3:GetBucketLocation",
                "s3:GetBucketPolicyStatus",
                "s3:GetBucketPublicAccessBlock",
                "s3:ListAccessPoints",
                "s3:ListAllMyBuckets"
            ],
            "Resource": "*"
        }
    ]
}

然后,我们将使用以下命令在 AWS 账户中创建策略:

$ aws iam create-policy --policy-name bespoke-pod-policy --policy-document file://s3-policy.json
{
    "Policy": {
        "PolicyName": "bespoke-pod-policy",
        "PermissionsBoundaryUsageCount": 0,
        "CreateDate": "2022-11-09T15:03:58Z",
        "AttachmentCount": 0,
        "IsAttachable": true,
        "PolicyId": "ANPARDV7UN626ZCPMFH4X",
        "DefaultVersionId": "v1",
        "Path": "/",
        "Arn": "arn:aws:iam::112233444:policy/bespoke-pod-policy",
        "UpdateDate": "2022-11-09T15:03:58Z"
    }
}

现在,我们已经拥有了创建角色所需的策略,并允许 EKS 集群 OIDC 提供者和 Kubernetes SA 组合来假设该角色。以下命令将帮助您完成这一操作。首先,我们需要为 AWS 账户、EKS OIDC 提供者、Kubernetes 命名空间和 SA 设置一些环境变量:

$ export account_id=$(aws sts get-caller-identity --query "Account" --output text)
$ export oidc_provider=$(aws eks describe-cluster --name myipv4cluster --region eu-central-1 --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")
$ export namespace=default
$ export service_account=s3-access

现在,我们可以使用以下命令创建 Pod 将要假设的角色的信任关系:

cat >trust-relationship.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::$account_id:oidc-provider/$oidc_provider"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "$oidc_provider:aud": "sts.amazonaws.com",
          "$oidc_provider:sub": "system:serviceaccount:$namespace:$service_account"
        }
      }
    }
  ]
}
EOF

这一切只是定义 EKS OIDC 提供者、Kubernetes 命名空间和 SA 的映射关系,因此我们需要使用以下命令创建具有此信任关系的角色,并附加之前创建的策略,以便使用 S3 访问:

$ aws iam create-role --role-name s3-access-default --assume-role-policy-document file://trust-relationship.json --description "s3 access role for pod SA s3-access/default"
{
    "Role": {
        ………
        "Arn": "arn:aws:iam::11223344:role/s3-access-default"
    }
}
$ aws iam attach-role-policy --role-name s3-access-default --policy-arn=arn:aws:iam::$account_id:policy/bespoke-pod-policy

现在我们已经在 AWS IAM 中设置好了所需的一切,接下来只需要配置一个 Pod,使其使用此处定义的命名空间中的 SA。我们从配置default命名空间中的 SA 开始:

$ cat >my-service-account.yaml <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: s3-access
  namespace: default
EOF
$ kubectl apply -f my-service-account.yaml
$ kubectl annotate serviceaccount -n $namespace $service_account eks.amazonaws.com/role-arn=arn:aws:iam::$account_id:role/s3-access-default
serviceaccount/s3-access annotated
$ kubectl describe sa $service_account
Name:                s3-access
Namespace:           default
Labels:              <none>
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::11223344:role/s3-access-default
Image pull secrets:  <none>
Mountable secrets:   s3-access-token-9z6
Tokens:              s3-access-token-9z6
Events:              <none>

我们现在可以使用以下命令运行带有 SA 的 Pod。此命令将使用aws-cli镜像并运行aws s3 ls命令,应该能够列出存储桶,因为分配的 SA 具有通过角色暴露的必要权限:

$ kubectl run –rm -ti cli  --image=amazon/aws-cli –overrides="{ 'spec': { 'serviceAccount': 's3-access' }  }" s3 ls
2022-09-29 09:26:05 ingress-123-bb
2022-03-29 17:49:50 servicecatalog456643
……..

如果将serviceAccount值更改为default,您会看到命令失败,因为默认的 SA 没有注解,因此没有映射到有效的 IAM 角色。现在,让我们来看一下如何使用 IaC 简化这个过程。

如何通过编程方式创建 IRSA 角色

我们已经使用了上一章中的eksctl create iamserviceaccount命令,允许托管存储控制器的 Pod 与 AWS 存储 API 进行通信。

如果我们查看通用命令,可以看到我们已将特定命名空间中的 Kubernetes SA 与特定 IAM 策略关联起来。该策略定义了可以执行的 API 操作,而将 SA 与 Pod 关联起来,使得该 Pod 可以执行该操作:

$ eksctl create iamserviceaccount --cluster=<clusterName> --name=<serviceAccountName> --namespace=<serviceAccountNamespace> --attach-policy-arn=<policyARN>

因此,与上一部分相比,我们唯一需要预先准备的是具有相关权限的策略,这样我们就可以提供policyARNeksctl工具将执行以下操作:

  1. 确定 EKS 集群的 OIDC 提供者。

  2. 使用派生的 OIDC 详细信息以及提供的 Kubernetes SA 名称和命名空间创建带有信任策略的角色。

  3. 将预先创建的策略附加到 IAM 角色。

  4. 在正确的命名空间中创建具有正确注解的 Kubernetes SA。

现在,您可以在命名空间中运行之前使用的kubectl run命令,然后使用在eksctl命令中指定的 SA,所有操作应该都能正常运行。接下来让我们看看如何在遇到问题时排查 IRSA。

如何排查 EKS 中的 IAM 问题

首先需要做的是确定这是否是 IAM 权限问题。如果我们查看以下示例中的错误信息,可以看到 AWS API 操作的AccessDenied错误信息——在这个例子中,是ListBuckets操作。这是一个明显的指示,表明这是一个 IAM 错误:

$ kubectl run -ti cli  --image=amazon/aws-cli --overrides='{ "spec": { "serviceAccount": "default" }  }' s3 ls
If you don't see a command prompt, try pressing enter.
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied

第一步是确定使用的是哪个 SA,然后从那里向后追溯。在这个例子中很清楚,因为我们有run命令。但是,假设我们没有它,我们可以使用下一个命令来找出它:

$ kubectl get po cli -o yaml | grep serviceAccountName
  serviceAccountName: default

然后,我们可以运行以下命令来确保注释已经设置,并识别将要假定的角色:

$ kubectl describe sa default
Name:                default
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   default-token-wjpnc
Tokens:              default-token-wjpnc
Events:              <none>

在这种情况下,没有注释,所以很明显这个 SA 没有权限。如果有角色被分配,我们可以使用以下命令来确定是策略还是权限问题:

$ kubectl describe sa default
…..
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::11223344:role/s3-access-default
…..
$ aws iam list-attached-role-policies --role-name s3-access-default
{
    "AttachedPolicies": [
        {
            "PolicyName": "bespoke-pod-policy",
            "PolicyArn": "arn:aws:iam::11223344:policy/bespoke-pod-policy"
        }
    ]
}

一旦我们可以看到附加的策略,在这个例子中只有一个,我们可以遍历它,但首先,你必须使用get-policy命令获取策略的版本:

$ aws iam get-policy --policy-arn arn:aws:iam::076637564853:policy/bespoke-pod-policy{
    "Policy": {
        "PolicyName": "bespoke-pod-policy",
        "Tags": [],
        "PermissionsBoundaryUsageCount": 0,
        "CreateDate": "2022-11-09T15:03:58Z",
        "AttachmentCount": 1,
        "IsAttachable": true,
        "PolicyId": "ANPARDV7UN626ZCPMFH4X",
        "DefaultVersionId": "v1",
        "Path": "/",
        "Arn": "arn:aws:iam::076637564853:policy/bespoke-pod-policy",
        "UpdateDate": "2022-11-09T15:03:58Z"
    }
}

现在我们可以使用get-policy-version命令提取权限。在以下示例中,缺少s3:ListAllMyBuckets操作,这就是导致问题的原因:

$ aws iam get-policy-version    --policy-arn arn:aws:iam::076637564853:policy/bespoke-pod-policy   --version-id v1
{
    "PolicyVersion": {
        "CreateDate": "2022-11-09T15:03:58Z",
        "VersionId": "v1",
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": [
                        "s3:GetAccountPublicAccessBlock",
                        "s3:GetBucketAcl",
                        "s3:GetBucketLocation",
                        "s3:GetBucketPolicyStatus",
                        "s3:GetBucketPublicAccessBlock",
                        "s3:ListAccessPoints"
                    ],
                    "Resource": "*",
                    "Effect": "Allow"
                }
            ]
        },
        "IsDefaultVersion": true
    }
}

你还可以考虑其他几个方面:

  • 你可以使用以下命令来检查分配的角色是否信任正确的 EKS OIDC 提供者:

    $ aws iam get-role --role-name
    
  • IRSA 通过一个变更 Webhook 来实现 Pod 身份验证。你可以使用以下命令验证这一点是否已经部署:

    $ kubectl get mutatingwebhookconfiguration pod-identity-webhook  -o yaml
    

其他一些错误在进一步阅读部分有介绍。在这一部分,我们探讨了 IRSA 是什么,它是如何工作的以及如何配置的,还讲解了如何进行一些基本的 IRSA 故障排除。接下来,我们将回顾本章的关键学习点。

总结

在本章中,我们探讨了如何使用实例配置文件和 IMDS 将 AWS API 权限分配给 EC2 实例(工作节点)和 Pods。我们还注意到,默认情况下,EKS Pods 继承它们运行的工作节点上分配的权限,这可能并不是一个好事,因为我们没有遵循最小权限模型,因为许多 Pods 可能根本不需要任何 AWS API 访问权限。

我们讨论了如何使用 IMDSv2 来减少工作节点权限的使用,并应与 IRSA 一起使用,以限制工作节点权限的继承。接着,我们演示了如何从命令行配置和使用 IRSA,以及像eksctl这样的 IaC 工具如何显著简化这一过程。最后,我们讨论了如何进行 AWS IAM 权限问题的基本故障排除,从 Kubernetes SA 向后追溯。

在下一章,我们将讨论如何使用 AWS 负载均衡器来使我们的 Kubernetes 服务更加稳健和可扩展。

进一步阅读

)

)

第十四章:在 EKS 上为应用设置负载均衡

第四章 中,我们探讨了如何使用 NodePort、Ingress 和/或 AWS 负载均衡器 (LB) 来暴露一个简单的应用。在本章中,我们将更详细地讨论如何使用 AWS 弹性负载均衡器 (ELB) 扩展和提升应用的弹性。

在大多数现代 Web 或云原生应用中,你需要确保应用对客户端可用(具备弹性),并且当 Kubernetes 调度器通过替换、移除和添加 Pods 来扩展应用时,应用能够应对这种变化。在应用前面放置负载均衡器可以将这些 Kubernetes 操作隐藏起来,使客户端无论 Pods 的位置或数量如何,都能通过一个一致的端点访问应用。因此,在本章中,我们将专门讨论以下主题:

  • 在 AWS 中有哪些负载均衡器(LB)可用,以及如何选择适合你需求的负载均衡器

  • 如何在 EKS 中创建和使用 AWS 负载均衡器

技术要求

你应该熟悉 YAML、AWS 身份与访问管理 (IAM) 和 弹性 Kubernetes 服务 (EKS) 架构。在开始本章之前,请确保你已经具备以下条件:

  • 连接到 EKS 集群 API 端点的网络

  • AWS kubectl 二进制文件已安装在你的工作站上

  • 对 AWS 网络的基本理解

为你的需求选择合适的负载均衡器

现代云应用的一个关键特性是能够横向扩展(增加更多实例以满足需求或从故障中恢复)。在 第四章 中,我们探讨了如何使用部署来创建和管理多个 Pods,但你还需要将用户流量分配到这些 Pods 上。这正是负载均衡器(LB)所做的事情,我们将在 EKS 中处理两种主要类型:应用程序负载均衡器 (ALB) 和 网络负载均衡器 (NLB),它们都是 ELB 类型。在接下来的两节中,我们将探讨适用于任何负载均衡器的两个关键概念。

概念 1 – 理解第四层和第七层负载均衡器的网络

当我们谈论网络中的层次时,我们指的是 开放系统互联 (OSI) 模型,该模型是在 1980 年代开发的,用以简化不同网络之间的互联。OSI 模型描述了一个七层模型,其中最上层,第七层(应用层),描述了应用程序使用的接口。第四层(传输层)描述了低层协议,如 用户数据报协议 (UDP)/传输控制协议 (TCP) 的行为。OSI 模型的完整描述超出了本书的范围,但值得注意的是,第三层(网络层)可用于描述底层网络,这在 99% 的情况下将是 IPv4 或 IPv6(除非你描述的是 广域网 (WAN))。

在 Layer 7 层运行的负载均衡器理解应用层协议,如 HTTP/HTTPS,并会使用 HTTP 路径/URL 检查并分发流量,还可以执行重定向和健康检查等操作。值得注意的是,由于 HTTPS 是加密的,负载均衡器必须充当代理(见 概念 2 – 理解代理和 DSR 模式),并且在大多数情况下,会在将流量转发到(后端)服务之前终止客户端的流量。

在 Layer 3/4 层运行的负载均衡器无法理解更高层的协议,如 HTTP/HTTPS,它们工作在较低层。这意味着它们可以支持 web 应用程序和其他流量,如 安全外壳协议SSH)或 简单邮件传输协议SMTP)。由于这些负载均衡器工作在较低层,因此无法检查 HTTP 头信息,因此它们检查和分发网络流量的方式相对简单,而且通常速度较快。这也意味着它们既可以作为 代理 运行,也可以在 直接服务器返回DSR)模式下运行。

下图展示了两种类型的负载均衡器。两种负载均衡器都能看到客户端流量(图示中的 1.1.1.1 IP 地址表示),并能执行以下操作:

  • 将 web 流量提供给 Layer 7 负载均衡器,并理解 HTTP 动词(图示中的 POST)以及路径或 URL(图示中的 /users/user

  • 在这些负载均衡器上分发流量,而 Layer 4 负载均衡器仅查看流量类型(在 HTTP/HTTPS TCP 的情况下)和端口(图示中的 8080

图 14.1 – L7 与 L4 负载均衡器比较

图 14.1 – L7 与 L4 负载均衡器比较

现在我们已经了解了不同网络层之间的差异,让我们回顾一下代理模式和 DSR 模式之间的差异。

概念 2 – 理解代理和 DSR 模式

要检查 HTTPS 流量(该流量是加密的),负载均衡器需要终止它,因此通常意味着它持有加密证书。这种模式被称为 反向代理模式,因为负载均衡器将代表客户端代理请求到后端服务器(s)。这一点可以从下图中看到,客户端的源 IP 地址被负载均衡器替代,这意味着请求和响应流量都通过负载均衡器:

图 14.2 – 代理和 DSR 模式比较

图 14.2 – 代理和 DSR 模式比较

在 DSR 中,返回/响应流量直接发送到客户端。这意味着负载均衡器处理的请求较少,并且由于引入的延迟较低,理论上可以更好地扩展。这也意味着后端(web 服务)需要知道客户端的 IP 地址,并能够将流量路由回去。以下表格描述了上图中每个步骤:

代理请求-响应 DSR 请求-响应
1 域名系统DNS)请求服务的 IP 地址解析为负载均衡器的 IP 地址,流量从客户端的 IP 地址发送到负载均衡器的 IP 地址。
2 HTTP/HTTPS 流量会被检查,并根据协议类型/URL/端口,流量会被发送到一组已注册的后端服务器(目标),通常基于其健康状况/负载或使用轮询方式。客户端 IP 地址通常会被添加到X-Forwarded-For头部,以便后端服务器可以识别请求来自何处,但这是可选的。 UDP/TCP 流量会被检查,并根据协议类型/端口,流量会被发送到一组已注册的后端服务器(目标),通常使用轮询方式,通过 Layer 2 地址(如X-header)进行,因此后端知道如何返回流量,但该方法会根据负载均衡器的实现方式有所不同。
3 请求由后端处理,X-Forwarded-for头部可用于验证请求。响应被发送回 LB。 请求由后端处理。响应通过保留的源 IP 地址的X-header或模仿 LB IP 返回给客户端。
4 LB 将响应返回给客户端。 对于 DR 路由器,这一步不需要,因为响应已经在上一步返回给客户端。

表 14.1 – 请求/响应步骤

现在我们已经了解了一个通用负载均衡器是如何工作的,让我们来看看 AWS 中可用的选项。

在 AWS 中有哪些负载均衡器可用?

AWS 中有多种类型的负载均衡器,但我们将重点讨论 EKS 常用的两种 ELB 类型:ALB 和 NLB。ELB 可以是外部的,也可以是内部的,但不能同时是两者。外部表示它可以从互联网访问,内部表示只能从虚拟私有云VPC)或具有路由访问该 ELB 所在 VPC 的内部地址访问。下图展示了 AWS 中可用的两种 ELB 选项:

图 14.3 – AWS ELB

图 14.3 – AWS ELB

这两种负载均衡器(LB)是通过三个关键概念来配置的:

  • 你创建一个 NLB 或 ALB 的实例,并将其连接到一个 VPC。

  • 你需要配置监听器,定义负载均衡器将接受流量的协议以及端口(NLB)或 URL 路径(ALB)。

  • 你配置一个目标组,定义从 LB 监听器发送到它的流量的目标。它还定义了需要的健康检查,以确保目标是健康的并且能够响应流量。

ALB 作为第 7 层代理工作,因此它可以处理 HTTP/HTTPS 和 HTTPv2。因此,如果你的应用程序使用 RESTful 或基于 gRPC 的 API,ALB 将能够支持这些流量。ALB 可以根据 注册 的 IP 地址(包括 VPC 地址和本地地址,在 RFC 1918 地址范围内,只要有 VPC 路由),EC2 实例和 Lambda 函数来将流量发送到目标。ALB 附加到 VPC 的可用区,并根据流量需求进行扩展。因此,AWS 提供了一个 DNS 规范名称CNAME)记录作为参考,因为它可能在 VPC 中有多个网络接口,而与 ALB 相关联的 IP 地址可能会发生变化。ALB 有一个相关联的安全组,意味着你可以控制对其公共或私有接口的访问。

重要提示

对 gRPC 的支持有限,因此虽然 ALB 可以转发 gRPC 流量,但它无法像处理 RESTful API 那样做流量分发决策。

NLB 位于 AWS 软件定义网络SDN)架构中,因此它的工作方式与 ALB 完全不同。NLB 作为一个第 4 层负载均衡器工作,根据后端服务器和协议的类型,支持代理模式和 DSR 模式。以下是这些模式的示例:

  • 如果后端服务是通过 EC2 实例 ID 来指定的,那么客户端的 IP 会被保留并显示在你的后端服务中。

  • 如果后端服务通过 IP 地址指定,那么我们会看到以下情况:

    • 如果目标组协议是 TCP/传输层安全TLS),客户端 IP 保持功能被禁用,后端服务将把负载均衡器视为流量的源(客户端 IP 地址可以通过代理协议头访问)

    • 如果目标组协议是 UDP/TCP_UDP,客户端 IP 会被保留并在后端服务中可见

即使后端看到客户端的 IP 地址,由于 NLB 是 SDN 构造,返回流量会通过 NLB 返回,而无需任何额外配置。这意味着客户端只会看到 NLB 的地址,并且不会出现不对称路由,即响应从一个 IP 发出,而返回的响应来自另一个 IP。

这种 返回流量魔法 并未由 AWS 解释,但它确保任何在 VPC 中的流量都会被路由回 NLB,即使客户端的 IP 地址是源 IP;如果是在 VPC 外部,你可能会遇到不同的不对称路由。

NLB 可以根据注册的 IP 地址(包括 VPC 地址和在 RFC 1918 地址范围内的本地地址,只要有 VPC 路由),EC2 实例和 ALB 将流量发送到目标。NLB 通过固定的弹性网络接口ENIs)附加到 VPC,因此 AWS 提供这些 IP 地址(公有或私有),一旦分配,IP 地址将保持不变且不会更改。NLB 没有关联的安全组,因此外部的 NLB 是开放的,能接入互联网。正如你所见,尽管这两种 ELB 有相似之处,但也存在差异。在接下来的部分,我们将看看如何选择适合的 ELB。

选择合适的 ELB

想象一下你想将运行在 EKS 上的简单微服务以可扩展和弹性的方式暴露给外部世界。你会选择哪个 ELB?当然,我们假设你想要的区域中两种 ELB 类型都可用!以下是一些你可能需要问的问题,帮助你做出决定:

  • 你要暴露的是哪种类型的接口?如果不是基于 HTTP/HTTPS 或 HTTPv2,你将需要使用 NLB。

  • 你是否希望卸载加密?如果是,在大多数情况下,你会想使用 SSL/TLS,而且在很多情况下,你会将加密/解密过程卸载到 ELB 上。NLB 和 ALB 都支持这一点,但如果你希望 Pod 进行加密/解密(端到端加密),你将需要一个 NLB,因为它可以将流量传递到 Pod。

  • 你需要高级的 Web 安全性吗?只有 ALB 与 AWS 的Web 应用防火墙WAF)和AWS Shield集成,后者作为分布式拒绝服务DDoS)保护服务。NLB 如果启用了 Shield Advanced 也可以进行保护,但它不与 WAF 集成,并且如前所述,没有关联的安全组。

  • 你需要静态 IP 地址来进行白名单管理吗?NLB 提供静态 IP 地址,而 ALB 使用 DNS 名称,可能会返回不同的 IP 地址。你可以将 NLB放在ALB 前面以提供静态 IP 地址,但这会增加一个网络跳跃,并增加延迟。

  • 你需要低延迟,还是可以应对大流量突发?由于 NLB 是 SDN 构建的,它的延迟非常低,且扩展速度远快于 ALB。

  • 在部署时,我是否想使用 Kubernetes 的 Ingress 或 Service?类似于 NLB,Kubernetes 服务(例如NodePort)是一个简单的构造,它将流量分配到一组 Pods。而 Ingress 则提供更多的路由控制,通过不同的路径将负载分配到你的 Pods 上,它类似于 ALB。所以,如果你希望不同的 Pods 处理用户和小组的更新,你可以使用基于路径的路由,将用户操作指向一个 Pod,将小组操作指向另一个 Pod。如果你需要一个 Pod 处理所有操作并暴露一个单一端口,那么你需要使用 NLB/Service。

当然,还有其他一些方面你可能需要考虑,但在选择使用哪种 ELB 时,这些是我认为有用的主要因素。在下一部分,我们将看看如何使用AWS 负载均衡器控制器ALBC)来创建和使用 ELB。

使用 EKS 创建并使用 AWS 负载均衡器

我们可以使用 ALBC,这是一个开源项目,来创建 ALB/NLB,或者使用我们 AWS 账户中已有的负载均衡器。让我们从安装控制器和配置正确的权限来访问 AWS API 开始。

在你的集群中安装 ALBC

成功安装 ALBC 所需的步骤如下:

  1. 首先,你需要确保你的 VPC 正确设置,以便如果你想创建一个内部或外部 ELB,它会部署到正确的子网中。你可以通过标记你的公共子网为kubernetes.io/role/elb,将私有子网标记为kubernetes.io/role/internal-elb并将Value设置为1来完成此操作。以下命令展示了如何标记并验证一个公共子网:

    $ aws ec2 create-tags --resources "subnetid-12" --tags Key=kubernetes.io/role/elb,Value=1
    $ aws ec2 describe-subnets  --subnet-ids subnetid-12 --query 'Subnets[].Tags[]'
    [{
            "Value": "myipv4cluster",
            "Key": "eksctl.cluster.k8s.io/v1alpha1/cluster-name"
        },
        ………
        {
            "Value": "1",
            "Key": "kubernetes.io/role/elb"
    ]
    
  2. 现在你已经标记了所有的子网,我们可以将控制器部署到你的集群中。我们将使用eksctl和 Helm。接下来我们需要做的是为控制器创建一个角色和服务账号(我们将使用 v2.4.5 版本,但这个版本可能会有所变化):

    $ curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.5/docs/install/iam_policy.json
    $ aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam-policy.json
    { "Policy": {"PolicyName": "AWSLoadBalancerControllerIAMPolicy",
    "Arn": "arn:aws:iam::112233:policy/AWSLoadBalancerControllerIAMPolicy",
            ……….}}
    
  3. 现在我们已经有了策略,我们可以使用前一个命令中提供的Arn来创建服务账号:

    $ eksctl create iamserviceaccount   --cluster myipv4cluster   --namespace kube-system   --name aws-load-balancer-controller   --attach-policy-arn arn:aws:iam::112233:policy/AWSLoadBalancerControllerIAMPolicy   --override-existing-serviceaccounts   --approve
    ………
    2022-12-12 18:46:51 [i]  created serviceaccount "kube-system/aws-load-balancer-controller"
    
  4. 现在我们已经创建了服务账号,我们可以部署控制器并使用刚刚创建的 Kubernetes 服务账号。为此,我们使用以下 Helm 命令,并且在之前的步骤中创建了服务账号:

    $ helm repo add eks https://aws.github.io/eks-charts
    $ helm repo update
    Hang tight while we grab the latest from your chart repositories...
    ...Successfully got an update from the "eks" chart repository
    ….
    Update Complete.
    $ helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system \
      --set clusterName=myipv4cluster \
      --set serviceAccount.create=false \
      --set serviceAccount.name=aws-load-balancer-controller \
      --set image.repository=602401143452.dkr.ecr.eu-central-1.amazonaws.com/amazon/aws-load-balancer-controller \
      --set region=eu-central-1 \
      --set vpcId=vpc-6575786587
    
  5. 接下来,我们使用以下命令验证部署和版本:

    $ kubectl get deployment -n kube-system aws-load-balancer-controller
    NAME      READY   UP-TO-DATE   AVAILABLE   AGE
    aws-load-balancer-controller   2/2 2     2           37s
    $ kubectl describe deployment  -n kube-system  aws-load-balancer-controller | grep Image
    Image: 602401143452.dkr.ecr.eu-central-1.amazonaws.com/amazon/aws-load-balancer-controller:v2.4.5
    

现在我们已经部署了控制器,让我们看看如何使用它。

使用 ALB 与应用程序

在这一部分,我们将使用官方示例,示例位于raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/examples/2048/2048_full.yaml。要将 ALB 与应用程序一起使用,请按照以下步骤操作:

  1. 我们做的第一件事是使用以下部分的 YAML 文件为应用组件创建命名空间:

    ---
    kind: Namespace
    apiVersion: v1
    metadata:
      name: game-2048
      labels:
        name: game-2048
    
  2. 接下来,我们将使用官方 Docker 镜像创建一个包含三个副本(Pod)的部署(请注意,你的工作节点必须能够访问互联网以拉取镜像),并使用以下代码片段在每个 Pod 的部署中暴露端口80

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      namespace: game-2048
      name: deployment-2048
    spec:
      selector:
        matchLabels:
          app.kubernetes.io/name: app-2048
      replicas: 3
      template:
        metadata:
          labels:
            app.kubernetes.io/name: app-2048
        spec:
          containers:
          - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
            imagePullPolicy: Always
            name: app-2048
            ports:
            - containerPort: 80
    
  3. 最后,我们创建一个NodePort服务,将 Pod 暴露到 EKS 集群之外,使用以下代码片段:

    ---
    apiVersion: v1
    kind: Service
    metadata:
      namespace: game-2048
      name: service-2048
    spec:
      ports:
        - port: 80
          targetPort: 80
          protocol: TCP
      type: NodePort
      selector:
        app.kubernetes.io/name: app-2048
    
  4. 到目前为止,我们并没有做任何不同于书中其他已部署服务的事情。关键的区别在于以下代码段,其中 Ingress 使用 alb.ingress 注释来定义要创建的 ALB 类型,在这种情况下,是一个基于 IP 地址的目标组的外部 ALB。如果你记得,ALB 是一个第七层代理,因此我们还需要定义要注册的路径和端口。在这种情况下,我们定义的是 loadbalancername:80/

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      namespace: game-2048
      name: ingress-2048
      annotations:
        alb.ingress.kubernetes.io/scheme: internet-facing
        alb.ingress.kubernetes.io/target-type: ip
    spec:
      ingressClassName: alb
      rules:
        - http:
            paths:
            - path: /
              pathType: Prefix
              backend:
                service:
                  name: service-2048
                  port:
                    number: 80
    
  5. 控制器(ALBC)将看到清单中的注释,并调用 AWS API 来创建一个监听端口 80 的外部 ALB,以及一个将 Pod 的 IP 地址定义为目标的目标组,这些目标将与我们在清单中创建的服务一起注册。

  6. 如果我们使用以下 kubectl 命令查看已部署的 Pods,可以看到已分配的 VPC IP 地址(输出已被截断):

    $ kubectl get po -o wide -n game-204
    NAME  READY   STATUS    RESTARTS   AGE   IP
    deployment-2048-7ff458c9f-fwx6h   1/1     Running   0      26m   192.168.9.154    ….
    deployment-2048-7ff458c9f-k5ns5   1/1     Running   0     26m   192.168.42.16    ….
    deployment-2048-7ff458c9f-v6mqw   1/1     Running   0          26m   192.168.87.245   …
    
  7. 现在,如果我们进入 AWS 控制台的 EC2 | 负载均衡器 部分,你将看到为游戏应用程序创建的 ALB 以及与之关联的 DNS 名称 值(请记住,IP 地址可能会变化,因此你需要始终使用提供的 DNS 名称)。以下截图展示了一个例子:

图 14.4 – 游戏应用程序 ALB

图 14.4 – 游戏应用程序 ALB

  1. 如果我们查看关联的 80,以下截图展示了一个例子:

图 14.5 – 游戏应用程序 ALB 目标组

图 14.5 – 游戏应用程序 ALB 目标组

  1. 如果我们打开目标组并查看 kubectl 命令,以下截图展示了一个例子:

图 14.6 – 游戏应用程序的已注册目标

图 14.6 – 游戏应用程序的已注册目标

你可以看到,所有的目标都在 15 秒 内显示 80,以确保每个 Pod 都能够接收流量:

图 14.7 – 游戏应用程序的健康检查

图 14.7 – 游戏应用程序的健康检查

  1. 如果你现在访问 ALB 的 DNS 名称,你将看到游戏界面并可以开始玩游戏,游戏内容包括在屏幕上移动方块:

图 14.8 – 游戏应用程序服务

图 14.8 – 游戏应用程序服务

现在,让我们看一下如何修改之前的 Kubernetes 配置,改用 NLB。

使用 NLB 与应用程序配合

我们可以重用之前示例中的 namespaceDeployment 配置,在 使用 ALB 与你的 应用程序 部分:

  1. 由于 NLB 是一个 L4 负载均衡器,我们不需要 Ingress 对象,但我们需要修改 Service 的定义。第一个区别是,我们使用的注解定义了与 ALB 注解相同的内容,即 NLB 类型和目标组。由于这是一个服务,我们定义的是端口而不是路径。在下面的代码块示例中,将创建一个外部 NLB,它将一个 IP 目标组暴露到端口 80,并映射到后台端口 80

    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: nlb-service
      namespace: game-2048
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-type: external
        service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
        service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    spec:
      ports:
        - port: 80
          targetPort: 80
          protocol: TCP
      type: LoadBalancer
      selector:
        app.kubernetes.io/name: app-2048
    
  2. 如前所述,控制器(ALBC)将查看注解,并调用 AWS API 来创建一个监听端口 80 的外部 NLB,以及一个由 selector 语句定义的 Pods IP 地址的目标组。

  3. 如果我们在 AWS 控制台上查看 EC2 | 负载均衡器,你将看到一个为游戏应用程序配置的 LB,但在这种情况下,它是一个 NLB。下面的截图展示了一个示例:

图 14.9 – 游戏应用程序 NLB

图 14.9 – 游戏应用程序 NLB

重要提示

虽然你可以在前面的示例中看到 DNS 名称,但 NLB 的 IP 地址是静态的,不像 ALB,后者在 AWS 扩展服务时 IP 地址可能会发生变化。

  1. 如果我们查看与 NLB 关联的 目标组已注册的目标,如下面的截图所示,我们可以看到相同的 Pod IP 地址与 NLB 关联,并且处于 健康 状态:

图 14.10 – 游戏应用程序 NLB 注册的目标

图 14.10 – 游戏应用程序 NLB 注册的目标

  1. 同样,一个健康检查已经自动创建,但由于这是一个第 4 层(L4)负载均衡器,健康检查是基于端口的。在下面的截图示例中,使用了 80 端口检查,确保每个 Pod 都能接收流量:

图 14.11 – 游戏应用程序 NLB 健康检查

图 14.11 – 游戏应用程序 NLB 健康检查

  1. 如果你浏览 NLB 的 DNS 名称,你将看到与 ALB 相同的游戏屏幕(图 14.8)。

重用现有的 LB

在某些情况下,你可能希望重用现有的 ELB。这样可以节省成本,同时简化配置。你可以通过使用 TargetGroupBinding 自定义资源来实现。这项资源允许你将服务与现有的 TargetGroup 和 NLB 关联。

假设我们已经创建了一个 NLB 和一个关联的基于 IP 的目标组,但还没有注册任何 IP 地址。以下代码片段展示了我们如何使用 TargetGroupBinding,该资源允许 ALBC 将与 Kubernetes 服务关联的 Pods 注册到现有的 NLB 或 ALB。关键是要在定义中指定给定的 TargetGroupARN

---
apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
  name: existing-nlb
  namespace: game-2048
spec:
  serviceRef:
    name: nlb-service
    port: 80
  targetGroupARN: arn:aws:elasticloadbalancing:eu-central-1:112233:targetgroup/existing-target-group/234

重要提示

NLB 位于与 Pods 运行的工作节点关联的安全组之外。因此,当你使用 TargetGroupBinding 时,需要允许来自 NLB ENI 的 TCP:80 流量到工作节点的安全组,因为 ALBC 并不会为你处理这部分。在 serviceRef 引用的服务中,可以将 type 配置为 ClusterIP,而不是 LoadBalancerNodePort

在本节中,我们回顾了负载均衡器的一般工作原理、AWS 中可用的选项,以及如何在 EKS 上配置和使用 ALBC。接下来,我们将重温本章的关键学习要点。

总结

在本章中,我们探讨了负载均衡器如何帮助应用程序扩展并提供弹性。它们通常分为两类,分别是第 7 层和第 4 层负载均衡器。第 7 层负载均衡器通常基于 HTTP 工作,理解特定协议的属性,如路径或头部,而第 4 层负载均衡器在端口级别工作,且与协议无关。

我们还回顾了代理模式和 DSR 模式之间的区别;代理负载均衡器始终位于客户端和后端系统之间,而在 DSR 模式中,后端可以直接将流量返回给客户端(尽管是通过伪造源地址为负载均衡器的地址)。

我们回顾了通常与 EKS 配合使用的不同类型的 ELB,分别是 ALB 和 NLB,以及它们之间的差异。接着我们学习了如何在 EKS 集群中安装 ALBC,并且如何使用注释和自定义配置创建 NLB 或 ALB,并将 Pods 注册到它们上,这样你就可以根据路径(ALB)或端口(NLB)访问服务。

最后,我们快速回顾了如何使用 TargetGroupBinding 将 Pods 注册到现有的 NLB(或 ALB)。现在你应该能够讨论在 AWS 中可用的不同选项,并配置 EKS 中的部署和服务,以利用 ALB 或 NLB。

在下一章中,我们将讨论如何使用 AWS Fargate 提供增强的安全性、支持小型工作负载,或简单地不管理或支付 EC2 实例的费用。

进一步阅读

第十五章:使用 AWS Fargate

在本书中,我们使用 弹性计算云EC2)实例作为我们的 弹性 Kubernetes 服务EKS)集群的工作节点,但 AWS Fargate 可以作为替代方案来托管 Pods。正如我们稍后会看到的,Fargate 可以为 Pod 提供一个更安全的操作环境,并且通常也更加具有成本效益(但并非总是如此)。

在某些情况下,您可能需要部署一个工作负载/应用程序,该应用程序运行不频繁,内存/CPU 占用较小,并且/或者需要增强的安全性,例如定期创建生产数据库的转储。Fargate 可以满足所有这些需求。在本章中,我们将详细探讨如何以及何时使用 AWS Fargate,以及它如何与 EKS 配合使用,提供 EC2 基础工作节点的替代方案。具体来说,我们将涵盖以下主题:

  • 什么是 AWS Fargate,它的定价如何

  • 如何在 EKS 中创建 Fargate 配置文件

  • 如何将 Pod 部署到 Fargate 实例

  • 如何排查常见的 Fargate 问题

技术要求

您应该熟悉 YAML、AWS 身份与访问管理IAM)和 EKS 架构。在开始本章之前,请确保以下内容已到位:

  • 您与 EKS 集群 API 端点之间的网络连接

  • 您的工作站已安装 AWS kubectl 二进制文件

  • 您对 AWS 网络和 EC2 有基本了解

什么是 AWS Fargate?

AWS Fargate 被开发为 EC2 的替代方案,提供一个 无服务器、容器原生的计算解决方案,具有三个关键设计原则:

  • 尽可能安全

  • 以便可靠并按需扩展

  • 成本效益

如果我们比较基于 EC2 的 EKS 工作节点与 Fargate 技术栈(如 图 15.1 所示),我们会发现它们非常相似。它们运行在物理服务器上,都有虚拟机操作系统和容器运行时来支持容器化应用程序。关键的区别是 Fargate 是无服务器的,这意味着您无需关心虚拟机操作系统、容器运行时等,因为这些都由 AWS 管理:

图 15.1 – AWS Fargate 与 EC2

图 15.1 – AWS Fargate 与 EC2

另一个主要区别是,Fargate 确实是为小型、突发性或批处理工作负载设计的,不同于传统上用于更稳定、长时间运行工作负载的 EC2。这意味着底层物理计算机群已优化以保持高利用率/密度,这反过来意味着它在操作上更高效,因此对消费者来说,使用它的成本可以更低。

以下工作负载适合使用 Fargate 而不是更传统的 EC2 部署:

  • 如果您的生产工作负载较小,偶尔会有突发情况,例如白天的零星网站流量,但晚上几乎没有流量

  • 如果你有一个偶尔使用的小型测试/非生产环境,那么 Fargate 可能比一个未充分利用的 EC2 实例更高效。

  • 如果你的工作负载由定期运行的任务组成,例如批处理或 cron 作业

另一个需要考虑的因素是 Firecracker,这是一个由 AWS 开发并开源的 虚拟机管理器 (VMM)。Firecracker 使用 MicroVM 的概念来创建一个小型、安全、隔离的环境,用于运行容器,这些容器可以非常快速地创建或销毁,并提供对 中央处理单元 (CPU)、随机存取存储器 (RAM)、磁盘和网络共享资源分配的精细控制:

图 15.2 – Fargate EKS 架构

图 15.2 – Fargate EKS 架构

Fargate 使用 EC2 Baremetal 实例作为其集群,并通过 Firecracker 在这些实例上为多个租户(客户)创建和管理 MicroVM。Firecracker 确保,尽管单个 Fargate EC2 主机可以支持来自不同 AWS 客户的多个 MicroVM,但它们都通过不同的 虚拟机 (VM) 边界控制进行隔离。

一个 MicroVM 将托管一个 Pod,这些 Pod 不会与其他 Pod 共享底层的内核、CPU 资源、内存资源或 弹性网络接口 (ENIs)。它们通过 Fargate EKS 代理进行编排,使得 K8s 调度器能够将 Pods 调度到 Fargate 集群,并通过 EKS AWS 虚拟私有云 (VPC) 容器网络接口 (CNI) 连接到客户 VPC 中的私有子网(不支持公共子网)。

K8s 调度器使用 Fargate 配置文件 来确定一个 Pod 规格是否满足部署到 Fargate 集群的要求。我们将在后续部分更详细地描述这个过程。

现在我们已经了解了整体架构,让我们来看一下 Fargate 的定价方式。

理解 Fargate 定价模型

Fargate 有一个简单的定价模型,基于你使用 MicroVM/Pod 的时长(按秒计费)以及分配给它的 vCPU/RAM 和磁盘。

如果我们首先查看一个 2 CPU/4 GB 内存、30 GB 磁盘的 EC2 实例,并且它 100% 的时间都在运行,那么基于按需定价,以法兰克福地区为例,成本大约为 $21/月

如果我们使用一个 2 CPU/4 GB 内存、30 GB 磁盘的 Fargate Pod,并且每天运行 5 小时,基于法兰克福地区的按需定价,成本大约为 $10/月

一开始我们可以看到,Fargate 的价格不到 EC2 实例的一半!然而,如果我们将 Pod 执行时间从每天 5 小时增加到 10 小时,那么成本将增加到每月 $35,这就比之前贵了不少。此外,请记住,使用 EC2 实例时,我们可以在同一个实例上运行多个 Pods,而不会产生额外费用;而使用 Fargate 时,每个 Pod 将额外收费 $10 或 $35(假设它们配置相同并运行相同的时间)。

从这个例子中你可以看出,虽然 Fargate 的定价模型易于理解,但从纯粹的成本角度来看,使用 Fargate 处理长时间运行的工作负载并不是很有效,但对于突发性、短期的工作负载,它将具有成本效益。然而,如果你考虑到管理和运营 EC2 实例的总成本,而不是 AWS 会管理和修补你的 Fargate 实例,你可能能够围绕 Fargate 建立一个商业案例。

你还需要记住,大多数 EC2 实例并不是 100% 被利用的;在许多情况下,它们的利用率几乎只有 30%。因此,如果你已经拥有一个大型 EC2 实例群(超过 100 个实例),Fargate 可能会为你节省大量费用,因为你可以通过减少 EC2 实例的成本来缓解 Fargate 较高费用的影响。现在我们已经了解了 Fargate 的定价模型,接下来让我们考虑如何配置 EKS 来使用 Fargate。

在 EKS 中创建 AWS Fargate 配置文件

理解 AWS Fargate 服务很有趣,但我们在本书中只涵盖了它,主要是为了给你提供一些背景知识。由于 Fargate 是无服务器的,你实际上只需要了解如何让 Kubernetes 调度器与 Fargate 服务通信,创建 MicroVM,连接到网络,并部署 Pod。这一切都是通过 Fargate 配置文件完成的,接下来的章节会详细讨论这个配置文件。

理解 AWS Fargate 配置文件的工作原理

在考虑如何将 Fargate 服务与 EKS 集成时,AWS 团队做出了一个有意识的决定,即不要求用户更新现有的 K8s 清单以支持 Fargate。相反,配置文件确定了哪些命名空间和/或标签将用于在 Fargate 上托管 Pods,并且在 Pod 定义中不需要进行任何更改。下图展示了这个过程是如何工作的:

图 15.3 – Fargate 配置文件工作流

图 15.3 – Fargate 配置文件工作流

上图所示的步骤详细如下:

  1. 当 API 服务器收到一个 Pod Create API 请求时(这可以是一个单独的 Pod 规范,部署的一部分,或其他任何内容),它会触发一个事件,这个事件会被 AWS 在 EKS 控制平面中安装和管理的自定义 webhook 捕获。

  2. 这个 webhook 查看 Fargate 配置文件,以确定正在使用的命名空间或标签是否由 Fargate 服务提供。如果匹配,它将更改调度器名称,使用Fargate 调度器而不是标准的 K8s 调度器。

  3. 现在,API 服务器将带有适当调度器名称的意图写入etcd,等待被调度。

  4. 如果调度器名称已经更改为 Fargate 调度器,那么它最终会被该调度器接管,调度器会负责从 AWS Fargate 集群请求计算资源,并协调创建 MicroVM 并将其附加到客户 VPC 上。Fargate 调度器是 AWS 在 EKS 控制平面中创建和管理的另一个组件。

如你所见,Fargate 配置文件控制了所有的工作原理。那么现在,让我们创建一个并看看它是如何工作的。

创建和调整 Fargate 配置文件

创建 Fargate 配置文件的最简单方法是使用eksctl。让我们首先使用以下命令创建一个新的命名空间来托管工作负载:

$ kubectl create namespace fargate-workload
namespace/fargate-workload created

然后,我们可以使用eksctl来创建(并验证)Fargate 配置文件,并指定新的命名空间作为 Fargate 的目标(默认情况下,每个集群最多可以有 10 个配置文件):

$ eksctl create fargateprofile --cluster myipv4cluster --name fargate --namespace fargate-workload
2022-12-16 10:42:23deploying stack "eksctl-myipv4cluster-fargate"
…
2022-12-16 10:47:12 created Fargate profile "fargate" on EKS cluster "myipv4cluster"
$ eksctl get fargateprofile --cluster myipv4cluster -o yaml
- name: fargate
  podExecutionRoleARN: arn:aws:iam::112233:role/eksctl-myipv4cluster-farga-FargatePodExecutionRole-1CD7AYHOTBDYO
  selectors:
  - namespace: fargate-workload
  status: ACTIVE
  subnets:
  - subnet-privat1
  - subnet-private2
  - subnet-private3

现在配置了配置文件,让我们看看如何使用它。

将 Pod 部署到 Fargate 实例

从之前的输出中可以看到,eksctl不仅创建了配置文件,还创建了一个执行角色,允许 Fargate Pods 使用 AWS 服务并自动分配 VPC 中的私有子网。

如果我们现在拿出本书中之前使用过的一个清单,并简单地将命名空间更改为fargate-workload命名空间并进行部署,我们会看到 Pod 被部署在 Fargate 实例上,而不是 EC2 工作节点上:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-web
  namespace: fargate-workload
spec:
  replicas: 1
  selector:
    matchLabels:
      app: simple-nginx-app
  template:
    metadata:
      labels:
        app: simple-nginx-app
    spec:
      containers:
        - name: nginx
          image: nginx

如果我们查看使用以下命令部署的 Pod,我们可以看到它运行在 Fargate 上:

$ kubectl get po -n fargate-workload -o wide
NAME  READY   STATUS    RESTARTS   AGE   IP  NODE ..
simple-web-12-   1/1     Running   0      11m   192.168.179.65    fargate-ip-192-168-179-65.eu-central-1.compute.internal

我们还可以通过以下命令验证 Pod 规范中调度器是否已正确设置,并且 MicroVM 是否已经作为节点注册到我们的集群中:

$ kubectl get pods -o yaml -n fargate-workload  simple-web-12 | grep schedulerName.
  schedulerName: fargate-scheduler
$ kubectl get node
……
fargate-ip-192-168-179-65.eu-central-1.compute.internal   Ready    <none>   18m   v1.20.15-eks-14c7a48
ip-192-168-12-212.eu-central-1.compute.internal           Ready    <none>   96d   v1.20.15-eks-ba74326
ip-192-168-63-61.eu-central-1.compute.internal            Ready    <none>   96d   v1.20.15-eks-ba74326
ip-192-168-70-114.eu-central-1.compute.internal           Ready    <none>   96d   v1.20.15-eks-ba74326

虽然 Pod 已经创建,但它只能从 VPC 内部访问(它将使用与 EC2 基础工作节点相同的安全组),因此我们可以添加一个 NLB 或 ALB,如在第十四章中所述。测试连接到 Fargate 上运行的 Pod 的一种快速方法是使用curl命令,以下是一个示例:

$ curl 192.168.179.65
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
…..
</html>
$

Fargate 有多个预配置的 vCPU 和内存大小;如果你没有指定 vCPU 和内存组合,那么将使用最小的可用组合(0.25 vCPU 和 0.5 GB 内存)。可以使用kubectl describe po命令来验证这一点,以下是一个示例:

$ kubectl describe po simple-web-12 -n fargate-workload
Name:                 simple-web-99b67d675-24ptk
Namespace:            fargate-workload
Priority:             2000001000
Priority Class Name:  system-node-critical
Node:                 fargate-ip-1.1.1.1.eu-central-1.compute.internal/192.168.179.65
Start Time:           Fri, 16 Dec 2022 11:46:40 +0000
Labels:               app=simple-nginx-app
                      eks.amazonaws.com/fargate-profile=fargate
                      pod-template-hash=99b67d675
Annotations:          CapacityProvisioned: 0.25vCPU 0.5GB
                      …….

如果我们调整初始部署中的 Pod 规格并设置一些限制,它将改变 Fargate 实例的大小。以下代码片段展示了一个使用内存和 CPU 限制的 K8s 清单示例:

containers:
        - name: nginx
          image: nginx
          resources:
            limits:
              memory: "2Gi"
              cpu: "2000m"

如果我们在更新部署后重新运行 describe 命令,我们可以看到配置的容量已经增加:

$ kubectl describe po simple-web-688f85f87d-gtxkb -n fargate-workload
Name:                 simple-web-688f85f87d-gtxkb
Namespace:            fargate-workload
Priority:             2000001000
Priority Class Name:  system-node-critical
Node:                 <none>
Labels:               app=simple-nginx-app
                      eks.amazonaws.com/fargate-profile=fargate
                      pod-template-hash=688f85f87d
Annotations:          CapacityProvisioned: 2vCPU 4GB

你可以看到,限制和 Fargate 注解/大小并没有完全对齐!这是因为在清单中为内存设置了 2 GiB,但实际分配了 4 GB。这是因为 Fargate 会尝试将清单配置与已定义的 CPU/内存配置匹配,并且会添加一些开销(246 MB 内存)以支持所需的 Kubernetes 组件(kubeletkube-proxycontainerd)。每个 Pod 还将获得 20 GB 的临时存储,可用于缓存数据,但当 Pod 被删除时,这些数据会被删除。

值得注意的是,如果你长时间运行 Pod,AWS 可能会为你的 Pod 打补丁,这可能导致它被驱逐并删除。为了缓解这个问题,你应该使用 Pod Disruption BudgetsPDBs)来保持一定数量的 Pods 并防止驱逐,如下面的代码片段所示:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: fg-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: simple-ninx-app

重要提示

根据你的 K8s 版本,你可能需要使用 apiVersion policy/v1beta1 的 beta 策略。

你可以使用以下命令验证 PDB 是否已设置:

$ kubectl get pod disruptionbudgets -n fargate-workload
NAME MIN AVAILABLE  MAX UNAVAILABLE  ALLOWED DISRUPTIONS   AGE
fg-pdb   1               N/A               0              43s

虽然 PDB 不能保证你的应用程序的弹性,但它可以在一定程度上确保操作问题不会影响它。由于 AWS 为你运营 Fargate,大多数事情都会无缝发生,但有时也会出现问题。在下一部分,我们将讨论一些常见问题以及如何排查它们。

在 Fargate 上排查常见问题

与容量相关的最常见问题是,有时平台资源不足,或者你想要的 CPU/RAM 配合不被支持。这会导致 Pod 状态始终为 PENDING。如果是平台问题,简单地等待并稍后重试(大约 15/20 分钟后)可能会解决问题;否则,调整 Pod 规格以支持不同的 CPU/RAM 配合。

如果你在运行 $ kubectl get nodes 命令时看到你的 Fargate 节点显示为 Not Ready,请确保它们使用的执行角色也已在 aws-auth ConfigMap 中配置,以下是一个示例:

mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      - system:node-proxier
      rolearn: <Pod_execution_role_ARN>
      username: system:node:{{SessionName}}

你可能会遇到 CoreDNS Pods 一直处于 PENDING 状态的问题;这通常是因为 VPC DNS 没有配置。解决方案是确保你的 VPC 中 enableDNSHostnamesenableDNSSupport 设置为 True

如果你在 Fargate 配置文件中遇到问题,确保目标命名空间和标签在你的集群和 Pod 规格中正确配置。需要考虑一些常见的处理规则:

  • Fargate 调度程序会匹配所有条件,因此,如果在配置文件中使用了命名空间和标签,那么它们必须在清单中同时使用,才能在 Fargate 实例上调度。

  • 如果存在多个 Fargate 配置文件且 Pod 匹配多个配置文件,则会使用随机的 Fargate 配置文件进行调度

在本节中,我们了解了 Fargate 的工作原理及其配置方式,并且掌握了一些基本的故障排除技巧。现在,我们将回顾本章的关键学习点。

总结

在本章中,我们探讨了 Fargate 是什么以及它是如何工作的。你了解到,Fargate 是 AWS 管理的服务,因此你只需要关注 Fargate 配置文件,并确保正确设置 VPC 网络,必要时设置负载均衡器,这样一切就能正常工作。

我们还探讨了这一技术,并发现 Fargate 在后台使用 Firecracker MicroVM 来提供完全的隔离,即使这些 Pod 位于同一集群中。

我们回顾了 Fargate 配置文件如何用于匹配 Pod 规格标签和配置文件中的命名空间,并将它们分配给 Fargate 调度程序,该调度程序负责与 AWS Fargate 服务进行协调,以在 Fargate MicroVM 上配置您的 Pod,并将其连接到您的 VPC。

接着,我们探讨了如何通过仅匹配 Fargate 配置文件中定义的命名空间和/或标签,使用不做更改的 Pod 或部署清单。我们还了解到,调整清单中的 LimitsRequests 资源将改变 MicroVM 的大小(前提是它与预定义的 CPU/RAM 组合之一相匹配)。

最后,我们回顾了一些 Fargate 常见问题及其解决方法。现在你应该能够描述何时使用 Fargate 来处理 EKS 工作负载,并能够配置 Fargate 配置文件,以便开发人员能够在 Fargate 实例上部署 Pods。

在下一章中,我们将探讨如何使用服务网格来提供更高的安全性或更好的遥测/日志记录。

进一步阅读

第十六章:使用服务网格

到目前为止,我们已经讨论了如何使用 AWS 和 K8s 网络控制(例如安全组和网络策略)来控制应用程序的进出流量。服务网格使你能够以更细粒度和一致的方式控制应用程序之间的通信流量,同时提供更好的流量可见性,并且提供诸如加密等附加功能。

随着团队在 EKS 中构建更大、基于微服务的生态系统,涉及成百上千个服务,控制和管理这些服务变成了一项全职工作。使用服务网格简化了这一过程,这意味着所有服务都可以以一致的方式进行管理,而无需每个开发团队修改其代码。在本章中,我们将深入探讨服务网格的工作原理,以AWS App Mesh为例。具体来说,我们将涵盖以下内容:

  • 探索服务网格及其优势

  • 在集群中安装 AWS App Mesh 控制器

  • 如何将你的应用程序与 App Mesh 集成

  • 使用 AWS Cloud Map 与 EKS 配合

  • 排查 Envoy 代理问题

技术要求

你应当对 YAML、AWS IAM 和 EKS 架构有所了解。在开始本章之前,请确保满足以下要求:

  • 你已经能够访问到 EKS 集群 API 端点

  • 你的工作站上已安装 AWS CLI、Docker 和 kubectl 二进制文件

  • 你对 AWS 和 K8s 网络有所基本了解

本章基于本书中已讨论的许多概念,因此建议你先阅读前几章。

探索服务网格及其优势

第七章中,我们回顾了什么是安全组,以及如何使用简单的 P 型规则(源/目标 IP 地址、源/目标端口和协议类型)来控制对工作节点(及其上运行的 Pods)的访问。在第九章中,我们探讨了如何使用 K8s 网络策略,通过 K8s 命名空间和标签来控制集群内的流量。

这两种方法的挑战在于它们相对静态,因此随着应用拓扑的变化,应用可能会进行扩展或缩减。例如,IP 地址可能会发生变化,这意味着需要修改配置。此外,随着服务数量的增加,确保配置正确、在多个集群间部署这些配置并监控其操作的运维负担变得越来越复杂和困难。

服务网格通过用一个控制平面替代多个控制点和配置来解决这些问题。控制平面可以在不同的命名空间/Pods(数据平面)中以一致的方式部署策略更改,动态响应应用拓扑的变化,并收集和展示网络流量遥测。大多数服务网格还将通过 API 展示其功能。以下图示展示了服务网格的一般架构:

图 16.1 – 一般服务网格架构

图 16.1 – 一般服务网格架构

现在让我们探索一些你可以选择的不同数据平面选项。

了解不同的数据平面解决方案选项

在选择如何实现服务网格数据平面时,有多个选项可供选择,这些选项提供了在不同命名空间、Pods 等之间一致的控制。这些选项如下:

  • 仅使用外部 DNS 服务提供服务发现

  • 使用 Linux 内核技术,如增强的 Berkeley 数据包过滤器eBPF),来提供流量控制和可见性

  • 使用 sidecar 容器控制所有网络流量并提供遥测数据

以下图示展示了这些不同的数据平面选项:

图 16.2 – 服务网格数据平面选项

图 16.2 – 服务网格数据平面选项

让我们更详细地了解每个数据平面选项。

探索使用 DNS 的服务发现

最简单类型的网格仅提供服务发现功能。这使得在 EKS 集群上运行的 Pods 能够定位在其他集群、其他 AWS 账户/VPC 或本地环境中运行的外部服务。这通常是通过使用coredns并将其配置为转发到外部 DNS 服务来实现的。外部 DNS 服务还可以用于注册集群服务,从而让外部用户能够定位到 K8s 集群。这可以通过使用 external-dns 来实现,external-dns 是一个 K8s 附加组件,它能够将 Kubernetes 资源与外部 DNS 服务同步。该附加组件可以与Route 53(标准 AWS DNS 服务)和Cloud Map(云服务发现工具)集成。稍后,在使用 AWS Cloud Map 与 EKS部分中,我们将探讨如何将 Cloud Map 与 EKS 集成,以提供简单的服务发现解决方案。这种类型的网格不支持任何形式的流量控制或遥测,但在需要将 K8s 服务与 AWS 或本地服务连接时非常有用。

探索基于内核的服务网格

提供流量控制或遥测的关键是实现网络过滤器,这些过滤器可以控制和记录流量流动。在今天的 K8s 中,通常是通过 kube-proxy 控制 iptables 来完成这项工作。当 K8s 资源(Pod、Deployment 和 Service)被部署时,kube-proxy 会写入必要的 iptables(或 IPVS)规则,以允许流量进出集群,并用正确的 NAT(网络地址转换)地址重写数据包。

iptables 的挑战在于它们是在网络速度相对较慢的时候设计的。因此,如果你实现了一个庞大的规则集,它们可能会变得很慢,因为每当进行更改时需要重新创建,而且需要线性评估。在一个大型的 EKS 集群中,你可能有超过 5000 条标准 iptables 规则,这些规则大多数是相同的,这会增加延迟。如果再加上复杂的应用规则,可能会严重影响网络栈。

iptables,它更加高效且灵活。eBPF 允许你在不改变内核参数的情况下在 Linux 内核中运行用户代码,并且在防火墙和深度数据包检测设备中使用广泛。由于其更高的性能,它在服务网格设计中使用得越来越多,并且与更新的 Kubernetes CNI 实现一起使用,支持部署过滤规则,以满足应用程序的网络连接需求。

本书中我们不会进一步讨论基于 eBPF 的服务网格,因为这是一个仍在发展的领域,但在评估服务网格时,值得考虑这一点。

探索基于侧车的服务网格

最常见的服务网格数据平面模式是使用侧车容器,它与应用程序容器部署在同一 Pod 中,并充当代理,控制进出流量,并在服务网格控制平面的监督下进行管理。这种方法的优点是应用程序网络规则被局部化到 Pod 中,不会影响内核,而且侧车可以用来支持增强的功能,如流量加密(互斥 TLS)。

侧车代理可以是自定义镜像,但大多数服务网格使用的是通用代理。Envoy (www.envoyproxy.io/) 是一个非常常见的选择,支持 HTTP/HTTPv2 代理、TLS 加密、负载均衡和可观测性(流量遥测)。让我们在下一节中通过研究 AWS App Mesh 来更详细地了解这一模式。

了解 AWS App Mesh

有许多不同的服务网格实现方式。我们将重点关注 AWS App Mesh,因为它是一个完全托管的服务,但请注意,其他网格如 Istio、Linkerd 和 Gloo 也可用(如果你想查看社区视角,可以访问layer5.io/service-mesh-landscape)。AWS App Mesh 提供了跨 Amazon EKS、AWS Fargate、Amazon ECS、Amazon EC2 和 EC2 上 Kubernetes 的一致网络控制,采用基于 Envoy 代理的 sidecar 数据平面。我们将重点讲解 EKS 的使用,但请记住,使用 AWS App Mesh 的主要原因之一是它能够在 AWS 的各种计算服务上提供跨应用的流量控制和可见性。

AWS App Mesh 实现了多个不同的构件来控制和监控应用流量。主要的构件是网格本身。你可以在一个账户中创建多个网格,每个网格代表所有应用/服务所在的逻辑网络边界。通常,你会使用单个网格来组织大量相关的服务,这些服务之间相互调用,并作为一个单一的生态系统运行。网格构件是需要首先创建的内容。下图展示了 AWS App Mesh 的主要构件:

图 16.3 – AWS App Mesh 虚拟构件

图 16.3 – AWS App Mesh 虚拟构件

一旦创建了网格,你将需要为每个 K8s 部署至少创建两个构件:

  • 虚拟节点是必需的,表示你的 K8s 部署/服务的抽象。它用于通过定义中使用的服务发现方法,将你的 K8s 资源与网格构件链接起来。

  • 虚拟服务是必需的,它可以指向虚拟节点或虚拟路由器,并供网格中的其他服务用于连接 K8s 服务。

重要提示

一个非常重要的点是,任何使用服务网格的消费者都将使用虚拟服务来访问底层的 K8s 服务,因此,在虚拟服务 awsName 键中定义的名称必须能够解析为一个 IP 地址(不管是什么 IP 地址)。如果你的所有服务都在集群中运行,那么你可以创建一个虚拟服务,这样本地的 CoreDNS 服务就会返回一个集群服务 IP 地址,之后当客户端/消费者发出 IP 请求时,Envoy sidecar 将进行转换。如果你的服务运行在 AWS 中的其他计算平台(例如 EC2)上,那么你需要集成到一个公共的外部 DNS 中,以便定位 EKS 服务。

AWS App Mesh 还支持两个可选构件:

  • 虚拟路由器,可用于在服务之间路由流量,对于蓝绿部署等场景非常有用。我们将在开始部署服务时讨论这种构件。

  • 虚拟网关,可以像 K8s Ingress 一样用于路由和控制南北流量。这种构造将在我们开始部署服务时进行讨论。

现在我们理解了基本构造,让我们来看看如何配置集群以与 AWS App Mesh 配合使用。

在集群中安装 AWS App Mesh 控制器

我们将使用 AWS App Mesh 控制器 for K8s(github.com/aws/aws-app-mesh-controller-for-k8s),它允许我们通过 K8s 清单来创建 App Mesh 资源,并自动将 Envoy 代理容器注入到 Pod 中。起点是创建控制器 Pods 所需的命名空间、IAM 角色和服务帐户。命令如下:

$ kubectl create ns appmesh-system
$ eksctl create iamserviceaccount --cluster myipv4cluster --namespace appmesh-system --name appmesh-controller --attach-policy-arn  arn:aws:iam::aws:policy/AWSCloudMapFullAccess,arn:aws:iam::aws:policy/AWSAppMeshFullAccess --override-existing-serviceaccounts --approve
…..
454 created serviceaccount "appmesh-system/appmesh-controller"

你会注意到,在提供 AWSAppMeshFullAccess 角色的同时,我们还提供了 AWSCloudMapFullAccess 角色,这将在 使用 AWS Cloud Map 与 EKS 章节中讨论。现在我们已经具备了前提条件,可以使用以下命令安装控制器并验证其是否正在运行:

$ helm install appmesh-controller eks/appmesh-controller --namespace appmesh-system --set region=eu-central-1 --set serviceAccount.create=false --set serviceAccount.name=appmesh-controller
…..
AWS App Mesh controller installed!
$ kubectl -n appmesh-system get all
NAME  READY   STATUS    RESTARTS   AGE
pod/appmesh-controller-xx   1/1     Running   0          105s
NAME TYPE    CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/appmesh-controller-webhook-service   ClusterIP   10.100.20.50   <none>        443/TCP   105s
NAME  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/appmesh-controller   1/1     1  1   105s
NAME  DESIRED CURRENT   READY   AGE
replicaset.apps/appmesh-controller-xx   1   1  1       105s
$ kubectl get crds | grep appmesh
….
virtualgateways.appmesh.k8s.aws 2022-12-20T21:45:42Z
virtualnodes.appmesh.k8s.aws    2022-12-20T21:45:42Z
virtualrouters.appmesh.k8s.aws  2022-12-20T21:45:42Z
virtualservices.appmesh.k8s.aws 2022-12-20T21:45:42Z

现在我们应该创建网格,它将作为任何服务在网格中传输的网络流量的逻辑边界。以下 K8s 清单将在当前集群区域创建一个名为 webapp 的简单网格:

apiVersion: appmesh.k8s.aws/v1beta2
kind: Mesh
metadata:
  name: webapp
spec:
  namespaceSelector:
    matchLabels:
      mesh: webapp

最后的步骤是将 AWSAppMeshEnvoyAccess 策略附加到工作节点的角色,以便所有 Envoy 容器都可以调用 App Mesh API。你可以为每个部署执行此操作,并为每个命名空间创建一个 IRSA。但在本书中,我们将仅更新节点。

现在我们已经配置了网格和工作节点,接下来让我们看看如何部署服务并配置相关的 App Mesh 构造。

将应用程序与 AWS App Mesh 集成

在本节中,我们将基于前几章中展示的许多细节,构建并部署一个应用程序,使用标准的 Kubernetes 资源,然后修改它以使用 AWS App Mesh 构造来控制和监控流量。可以从两个维度来考虑这些应用程序流量:来自消费者/用户/互联网的流量,通常称为南北流量;以及来自集群或生态系统中其他服务/应用程序的流量,称为东西流量。下图说明了这些概念:

图 16.4 – 典型的服务网格控制

图 16.4 – 典型的服务网格控制

南北流量通常需要某种身份验证/授权。此类流量的端点通常由 前端 服务处理,这些服务提供大量的展示逻辑,并且会聚合或编排跨多个后端服务的请求。东西流量通常来自其他系统(机器对机器),端点由 后端 服务提供,后端服务倾向于授权请求并为特定领域(如订单、账户等)提供业务数据。

大多数服务网格专注于保护、控制和监控东西流量,而南北流量则由标准 K8s 服务(例如 K8s Ingress)处理。然而,随着这些网格的发展,它们也开始处理更多的南北流量,取代了这些服务。

在接下来的部分中,我们将部署一个简单的前端/后端应用程序,并使用 K8s Ingress(一个 AWS ALB),然后修改后端以使用 AWS App Mesh(东西流量),用虚拟网关(南北流量)替换前端,并对流量监控和可观察性进行高层次的概述。

部署我们的标准应用程序

我们将使用两个基于curl的 Pod 来访问我们的 HTTP 服务。应用程序设计和 Python 代码片段显示在下图中。在初始部署中,我们只是假设蓝色和绿色表示不同的服务:

图 16.5 – 示例应用程序

图 16.5 – 示例应用程序

我们将从部署绿色服务开始。

部署绿色服务

该服务由两个 Python/FastAPI Pod 的部署组成,这些 Pod 在8081端口上公开两个路径;一个 GET /id路径,简单地返回{"id" : "green"}消息;另一个 GET /query路径,简单地返回{"message" : "hello from green"}消息:

  1. 让我们首先为这些资源创建命名空间。以下清单将在一个单独的命名空间green中创建图 16.5 中显示的资源,该命名空间没有应用网格标签:

    ---
    kind: Namespace
    apiVersion: v1
    metadata:
      name: green
      labels:
        name: green
    
  2. 现在我们创建一个Deployment,它使用已经容器化并推送到私有 ECR 仓库的前端代码(请查看第十一章中的相关说明和工件):

    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      namespace: green
      name: green-v1
    spec:
      selector:
        matchLabels:
          app.kubernetes.io/name: green-v1
      replicas: 2
      template:
        metadata:
          labels:
            app.kubernetes.io/name: green-v1
        spec:
          containers:
          - image: 112233.dkr.ecr…/green:0.0.1
            imagePullPolicy: Always
            name: backend
            ports:
            - containerPort: 8081
    

重要提示

图 16.5中展示了绿色和蓝色容器镜像中使用的 Python/FastAPI 代码,但为了本练习的目的,任何 Web 服务器都可以使用。

  1. 接下来,我们创建一个ClusterIP服务,用于在集群内访问绿色服务:

    ---
    apiVersion: v1
    kind: Service
    metadata:
      namespace: green
      name: green-v1
      labels:
        version: v1
    spec:
      ports:
        - port: 8081
          protocol: TCP
      selector:
        app.kubernetes.io/name: green-v1
    
  2. 现在,使用以下命令,我们可以看到有一个 K8s 服务,其他 K8s Pod 或服务可以找到并使用它:

    $ kubectl get svc -n green
    NAME  TYPE   CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
    green-v1  ClusterIP   10.100.192.178   <none>  8081/TCP   8h
    

既然我们已经有了绿色服务,接下来让我们在以下部分中部署蓝色服务。

部署蓝色服务

蓝色服务遵循与绿色服务类似的模型,由两个 Python/FastAPI 容器的部署组成,这些容器在8080端口上公开两个路径;一个 GET /id路径,简单地返回{"id" : "blue"}消息;另一个 GET /query路径,简单地返回{"message" : "hello from blue"}消息。该服务具有一个ClusterIP服务,只能在集群内部使用:

  1. 让我们使用以下清单为我们的应用程序创建蓝色命名空间,该命名空间没有应用网格标签:

    ---
    kind: Namespace
    apiVersion: v1
    metadata:
      name: blue
      labels:
        name: blue
    
  2. 现在,我们创建引用 ECR 中后端容器的Deployment

    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      namespace: blue
      name: blue-v1
    spec:
      selector:
        matchLabels:
          app.kubernetes.io/name: blue-v1
      replicas: 2
      template:
        metadata:
          labels:
            app.kubernetes.io/name: blue-v1
        spec:
          containers:
          - image: 112233.dkr.ecr…blue:0.0.1
            imagePullPolicy: Always
            name: blue-v1
            ports:
            - containerPort: 8080
    

重要提示

在绿色和蓝色容器镜像中使用的 Python/FastAPI 代码如图 16**.5所示,但任何 Web 服务器都可以用于本练习的目的。

  1. 最后,我们创建服务ClusterIP,它将创建必要的集群 DNS 条目,以便从集群内部访问该服务:

    ---
    apiVersion: v1
    kind: Service
    metadata:
      namespace: blue
      name: blue-v1
      labels:
        version: v1
    spec:
      ports:
        - port: 8080
          protocol: TCP
      selector:
        app.kubernetes.io/name: blue-v1
    
  2. 使用以下命令,我们可以看到有一个 K8s 服务,以便其他 K8s 服务/Pods 可以定位并使用它:

    $ kubectl get svc -n blue
    NAME   TYPE   CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
    blue-v1   ClusterIP   10.100.223.120   <none> 8080/TCP   8h
    

最后,我们将部署消费者服务,并使用所有原生的非网格资源测试与蓝色和绿色服务的连接性。

部署消费者服务

最后,我们将部署消费者服务,该服务由一个单独的 Pod 组成,支持curl命令:

  1. 使用以下清单将创建如图 16**.5所示的资源,在一个单独的命名空间consumer中,该命名空间没有应用网格标签:

    kind: Namespace
    apiVersion: v1
    metadata:
      name: consumer
      labels:
        name: consumer
    
  2. 现在我们创建一个Deployment,它引用从公共 Docker Hub 仓库拉取的alpine/curl容器:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      namespace: consumer
      name: consumer
    spec:
      selector:
        matchLabels:
          app.kubernetes.io/name: consumer
      replicas: 1
      template:
        metadata:
          labels:
            app.kubernetes.io/name: consumer
        spec:
          containers:
          - image: alpine/curl
            command:
            - sleep
            - "36000"
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 22
            name: consumer
          restartPolicy: Always
    
  3. 我们可以使用以下命令检查 Pod 是否已部署:

    $ kubectl get po -n consumer
    NAME                    READY   STATUS    RESTARTS   AGE
    consumer-123   1/1     Running   0          13s
    
  4. 完成了!现在我们可以连接到我们的消费者 Pod,并使用以下命令测试是否可以连接到相关的 K8s 服务:

    $ kubectl exec -it -n consumer consumer-123 – sh
    / # curl http://green-v1.green:8081/id
    {"id":"green"}
    / # curl http://green-v1.green:8081/query
    {"message":"hello from green"}
    / # curl http://blue-v1.blue:8080/id
    {"id":"blue"}
    / # curl http://blue-v1.blue:8080/query
    {"message":"hello from blue"}
    

重要提示

请注意,我们使用了简化的服务表示法<scv-name>.namespace,来调用我们在每个命名空间中创建的 K8s 服务。

现在我们已经有了一组正常工作的服务,我们将添加基本的网格组件并再次测试,但这次使用服务网格虚拟服务。

添加基本的 AWS App Mesh 组件

我们需要做的第一件事是标记bluegreenconsumer命名空间,以确定使用哪个网格,并确认我们希望将 Envoy sidecar 容器自动注入到部署到这些命名空间中的所有 Pods。以下命令展示了如何为示例应用程序完成这一操作:

$ kubectl label namespace consumer mesh=webapp
$ kubectl label namespace consumer appmesh.k8s.aws/sidecarInjectorWebhook=enabled
$ kubectl label namespace blue mesh=webapp
$ kubectl label namespace blue appmesh.k8s.aws/sidecarInjectorWebhook=enabled
$ kubectl label namespace green mesh=webapp
$ kubectl label namespace green appmesh.k8s.aws/sidecarInjectorWebhook=enabled

重要提示

通常,命名空间标签会在创建命名空间时自动创建;我们现在这样做仅仅是为了在本书中说明这些概念。

下一步是部署 App Mesh 的VirtualNode,这是我们重新部署应用程序所必需的。这对于每个需要使用网格的 K8s 部署都必须完成,因为它将允许 Envoy 代理正确配置自己。在这一部分中,我们首先展示了绿色和蓝色服务的配置;消费者服务将在最后配置,因为它引用了这两个服务。

绿色的VirtualNode清单在以下片段中显示,并引用了我们在基本应用程序部署过程中创建的green-v1 K8s 服务。它还创建了一个基本的健康检查,使用/id路径,定义了 DNS 作为底层资源的服务发现协议,并使用 K8s 服务的完全限定名称:

---
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualNode
metadata:
  name: green-v1
  namespace: green
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: green-v1
  listeners:
    - portMapping:
        port: 8081
        protocol: http
      healthCheck:
        protocol: http
        path: '/id'
        healthyThreshold: 2
        unhealthyThreshold: 2
        timeoutMillis: 2000
        intervalMillis: 5000
  serviceDiscovery:
    dns:
      hostname: green-v1.green.svc.cluster.local

蓝色的VirtualNode清单在以下片段中显示,并引用了blue-v1 K8s 服务,但与绿色的VirtualNode配置方式相同:

---
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualNode
metadata:
  name: blue-v1
  namespace: blue
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: blue-v1
  listeners:
    - portMapping:
        port: 8080
        protocol: http
      healthCheck:
        protocol: http
        path: '/id'
        healthyThreshold: 2
        unhealthyThreshold: 2
        timeoutMillis: 2000
        intervalMillis: 5000
  serviceDiscovery:
    dns:
      hostname: blue-v1.blue.svc.cluster.local

我们现在可以检查虚拟节点是否已在集群中部署,并已在 AWS App Mesh API 中注册,使用以下命令:

$ kubectl get virtualnode --all-namespaces
NAMESPACE   NAME  ARN                AGE
blue  blue-v1    arn:aws:appmesh:eu-central-1:112233:mesh/webapp/virtualNode/blue-v1_blue     2m50s
green       green-v1   arn:aws:appmesh:eu-central-1:112233:mesh/webapp/virtualNode/green-v1_green   103s
$ aws appmesh list-virtual-nodes --mesh-name webapp
{
    "virtualNodes": [
        {
….
            "virtualNodeName": "blue-v1_blue",
….
        },
        {
….
            "virtualNodeName": "green-v1_green",
…..}]}

注意

使用 AWS CLI 检查资源是否已完全部署到网格中总是值得的,因为有时资源已在 K8s 中部署,但配置不正确,因此不会出现在网格 API 中。

实际的 Deployment 和 Pods 尚未发生变化,因此,如果你列出任何一个命名空间中的 Pods,你会看到原始的 Pods。我们现在可以使用kubectl rollout命令重新启动 Deployment,并且会看到bluegreen命名空间中 Pods 的容器数量增加。以下是blue命名空间的命令示例:

$ kubectl get po -n blue
NAME                      READY   STATUS    RESTARTS   AGE
blue-v1-684cc59d8-5kczs   1/1     Running   0          23h
blue-v1-684cc59d8-nfvf9   1/1     Running   0          23h
$ kubectl rollout restart deployment blue-v1 -n blue
deployment.apps/blue-v1 restarted
$ kubectl get po -n blue
NAME                       READY   STATUS    RESTARTS   AGE
blue-v1-6bdfb49995-8789s   2/2     Running   0          8s
blue-v1-6bdfb49995-zzsw4   2/2     Running   0          10s

最后一步是为蓝色和绿色 Pods 添加VirtualService资源,因为目前,尽管 Envoy 代理已注入并与VirtualNode配置一起进行了配置,但该服务在网格中不可解析。如以下清单所示,blue服务的VirtualService使用服务名blue,但将直接映射到我们之前在blue命名空间中创建的blue-v1虚拟节点:

---
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualService
metadata:
  name: blue
  namespace: blue
spec:
  awsName: blue.blue.svc.cluster.local
  provider:
    virtualNode:
      virtualNodeRef:
        name: blue-v1
        namespace: blue

如果你还记得,在理解 AWS App Mesh部分中,我们提到过awsName需要能够通过 DNS 解析。由于此服务完全在 K8s 中运行,我们可以在blue命名空间中添加一个名为blue的虚拟 K8s 服务,以便能够使用以下清单解析blue.blue.svc.cluster.local服务名:

---
apiVersion: v1
kind: Service
metadata:
  name: blue
  namespace: blue
  labels:
    app.kubernetes.io/name: blue
spec:
  ports:
  - port: 8080
    name: http

`注意

请记住,尽管 DNS 查找会返回与blue服务关联的集群 IP 地址,但 Envoy 代理会修改流量,使其能够与底层的blue-v1服务进行通信。

部署了VirtualServices和虚拟 K8s 服务到两个命名空间后,我们可以使用以下命令检查配置:

$ kubectl get virtualservice --all-namespaces
NAMESPACE   NAME    ARN                             AGE
blue   blue    arn:aws:appmesh:eu-central-1:112233:mesh/webapp/virtualService/blue.blue.svc.cluster.local     37s
green       green   arn:aws:appmesh:eu-central-1:112233:mesh/webapp/virtualService/green.green.svc.cluster.local   25s
$ kubectl get svc --all-namespaces
NAMESPACE    NAME     TYPE  CLUSTER-IP       ..
blue  blue     ClusterIP   10.100.217.243   <none>  8080/TCP .
blue  blue-v1  ClusterIP   10.100.50.46     <none>  8080/TCP .
green green    ClusterIP   10.100.100.13    <none>  8081/TCP .
green green-v1 ClusterIP   10.100.51.214    <none>  8081/TCP .
….

我们还可以通过aws appmesh list-virtual-services命令或控制台查看已创建的虚拟服务,以下图为示例:

图 16.6 – K8s 服务的网格虚拟服务控制台视图

图 16.6 – K8s 服务的网格虚拟服务控制台视图

我们现在已经定义了蓝色和绿色服务的所有资源。接下来,我们可以为消费者添加VirtualNode并测试与网格服务的连接性。消费者VirtualNode的清单如下所示,类似于蓝色和绿色服务的定义;不过,它包含一个后端键,允许它与我们在部署标准应用程序部分中创建的bluegreen服务进行通信(这也是我们最后执行此操作的原因):

---
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualNode
metadata:
  name: consumer
  namespace: consumer
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: consumer
  listeners:
    - portMapping:
        port: 8082
        protocol: http
  backends:
    - virtualService:
        virtualServiceRef:
          namespace: blue
                     name: blue
    - virtualService:
        virtualServiceRef:
                    namespace: green
                   name: green
  serviceDiscovery:
    dns:
      hostname: consumer.consumer.svc.cluster.local

部署VirtualNode配置后,我们可以重新部署消费者部署,并使用以下命令检查结果资源:

$ kubectl rollout restart deployment consumer  -n consumer
deployment.apps/consumer restarted
$ kubectl get po -n consumer
NAME          READY   STATUS    RESTARTS   AGE
consumer-1122\. 2/2     Running   0          105s
$ aws appmesh describe-virtual-node --virtual-node-name consumer_consumer --mesh-name webapp
{
    "virtualNode": {
        "status": {
            "status": "ACTIVE"
….}

我们现在可以exec进入我们的消费者 Pod,并使用以下命令测试我们的 App Mesh 服务:

$ kubectl exec -it -n consumer consumer-1122  -- sh
Defaulted container "consumer" out of: consumer, envoy, proxyinit (init)
/ # curl -s http://blue.blue.svc.cluster.local:8080/id
{"id":"blue"}
/ # curl -s http://green.green.svc.cluster.local:8081/id
{"id":"green"}

你可能会注意到,由于这是一个多容器 Pod,exec命令默认执行在应用容器中,在这种情况下是consumer,但是你也会看到envoyinit容器(proxyinit)已被注入到原始的 Pod 定义中。我们现在也使用完全限定的 App Mesh 服务名称,例如blue.blue.svc.cluster.local,而不是 K8s 服务名称,比如blue-v1

我们现在已经部署了网格并将其集成到我们的应用中。从应用的角度来看,唯一变化的是我们在curl命令中使用的服务名称。虽然工作量还是比较大,但在接下来的部分中,我们将看看虚拟路由器如何简化蓝绿部署。

在 AWS App Mesh 中使用虚拟路由器

在这个示例中,我们假设bluegreen服务现在是同一个服务的不同版本(蓝绿部署)。我们将创建一个新的服务myapp,表示该应用程序,然后在现有的两个虚拟节点之间放置一个虚拟路由器,并最初将所有流量发送到绿色版本。下图说明了这一点:

图 16.7 – 将虚拟路由器添加到我们的服务中

图 16.7 – 将虚拟路由器添加到我们的服务中

我们需要做的第一件事是创建新的VirtualRouter。以下清单创建了一个路由器,并为/id路径映射了一个单独的路由,监听 TCP 端口8085weight键用于定义流向指定VirtualNode的流量权重/百分比。在下面的示例中,我们将所有流量发送到green-v1VirtualNode

---
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualRouter
metadata:
  name: app-router
  namespace: consumer
spec:
  listeners:
    - portMapping:
        port: 8085
        protocol: http
  routes:
    - name: app-route
      httpRoute:
        match:
          prefix: /id
        action:
          weightedTargets:
            - virtualNodeRef:
                name: green-v1
                namespace: green
              weight: 100
              port: 8081
            - virtualNodeRef:
                name: blue-v1
                namespace: blue
              weight: 0
              port: 8080

我们还需要添加myapp虚拟服务和虚拟 K8s 服务(用于 DNS 解析);以下示例清单创建了这两个服务,并引用了我们之前创建的VirtualRouter,而不是VirtualNode

---
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualService
metadata:
  name: myapp
  namespace: consumer
spec:
  awsName: myapp.consumer.svc.cluster.local
  provider:
    virtualRouter:
      virtualRouterRef:
        name: app-router
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: consumer
  labels:
    app.kubernetes.io/name: consumer
spec:
  ports:
  - port: 8085
    name: http

一旦我们部署了这些资源,我们需要调整消费者VirtualNode的规格,通过添加新的后端配置来允许访问这个新服务,如下所示:

backends:
    - virtualService:
        virtualServiceRef:
          namespace: blue
          name: blue
    - virtualService:
        virtualServiceRef:
          namespace: green
          name: green
    - virtualService:
        virtualServiceRef:
          name: myapp

重要提示

由于myapp服务与consumer处于同一命名空间,我们不需要添加namespace键,但为了清晰起见,你可能会想要添加它。

完成所有部署后,我们现在可以exec进入consumer并使用以下命令测试我们的新服务:

$ kubectl exec -it -n consumer consumer-1122  -- sh
Defaulted container "consumer" out of: consumer, envoy, proxyinit (init)
/ # curl -s http://myapp.consumer.svc.cluster.local:8085/id
{"id":"green"}
/ # curl -s http://myapp.consumer.svc.cluster.local:8085/id
{"id":"green"}

如果我们现在调整VirtualRouterroutes配置中的权重,以便在bluegreen服务之间均匀分配流量,我们可以将流量从green服务转移到blue服务,如下片段所示:

routes:
    - name: app-route
      httpRoute:
        match:
          prefix: /id
        action:
          weightedTargets:
            - virtualNodeRef:
                name: green-v1
                namespace: green
              weight: 50
              port: 8081
            - virtualNodeRef:
                name: blue-v1
                namespace: blue
              weight: 50
              port: 8080

现在运行相同的curl命令将会从bluegreen服务中返回响应:

/ # curl -s http://myapp.consumer.svc.cluster.local:8085/id
{"id":"green"}
/ # curl -s http://myapp.consumer.svc.cluster.local:8085/id
{"id":"blue"}
/ # curl -s http://myapp.consumer.svc.cluster.local:8085/id
{"id":"green"}
/ # curl -s http://myapp.consumer.svc.cluster.local:8085/id
{"id":"blue"}

再次强调,这里应用程序唯一的变化是使用的 URL/服务,Envoy 和 AWS App Mesh 负责所有的魔法。到目前为止,我们只关注东西向流量;在下一部分,我们将探讨如何通过虚拟网关将该服务暴露到集群外部。

在 AWS App Mesh 中使用虚拟网关

VirtualGateway用于暴露在网格内运行的服务,并使没有访问 App Mesh 控制平面的外部服务通过配置的 Envoy 代理访问它。它通过部署独立的 Envoy 代理和 AWS VirtualService来实现,后者将流量传递到VirtualRouter或直接传递到VirtualNode。我们将扩展myapp服务,使其可以通过VirtualGateway从互联网访问。下图说明了这一点:

图 16.8 – 虚拟网关部署

图 16.8 – 虚拟网关部署

我们需要做的第一件事是创建并标记internet命名空间,该命名空间将承载我们的 Ingress 网关和负载均衡器。我们这样做是因为我们在独立模式下托管 Envoy 代理,所以不希望使用会尝试在独立 Envoy 代理上注入 Envoy 代理的命名空间。以下命令说明了如何操作:

$ kubectl create namespace internet
$ kubectl label namespace internet gateway=in-gw
$ kubectl label namespace internet mesh=webapp

然后,我们可以继续创建VirtualGateway,它将在我们刚创建的internet命名空间中监听端口8088,使用以下清单:

---
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualGateway
metadata:
  name: in-gw
  namespace: internet
spec:
  namespaceSelector:
    matchLabels:
      gateway: in-gw
  podSelector:
    matchLabels:
      app: in-gw
  listeners:
    - portMapping:
        port: 8088
        protocol: http

我们现在可以使用以下清单在consumer命名空间中创建myapp VirtualService

---
apiVersion: appmesh.k8s.aws/v1beta2
kind: GatewayRoute
metadata:
  name: myapp-route
  namespace: internet
spec:
  httpRoute:
    match:
      prefix: "/"
    action:
      target:
        virtualService:
          virtualServiceRef:
            name: myapp
            port: 8085
            namespace: consumer

重要提示

我们将使用默认前缀/,它会捕获所有流量并将其发送到myapp VirtualService

使用以下命令,我们现在可以获取创建的VirtualGateway的 ARN,因为在部署独立 Envoy 代理时需要此信息:

$ kubectl get virtualgateway --all-namespaces
NAMESPACE   NAME          ARN                   AGE
internet    in-gw   arn:aws:appmesh:eu-central-1:112233:mesh/webapp/virtualGateway/in-gw_internet   7m37s

我们现在可以将独立的 Envoy 代理部署到internet命名空间。在以下示例清单中,我们使用 AWS Envoy 镜像创建两个副本,并使用APPMESH_RESOURCE_ARN环境变量注入前一步中列出的VirtualGateway的 ARN:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: in-gw
  namespace: internet
spec:
  replicas: 2
  selector:
    matchLabels:
      app: in-gw
  template:
    metadata:
      labels:
        app: in-gw
    spec:
      containers:
        - name: envoy
          image: 840364872350.dkr.ecr.eu-central-1.amazonaws.com/aws-appmesh-envoy:v1.24.0.0-prod
          env:
          - name: APPMESH_RESOURCE_ARN
            value: "arn:aws:appmesh:eu-central-1:112233:mesh/webapp/virtualGateway/in-gw_internet"
          - name: ENVOY_LOG_LEVEL
            value: "debug"
          ports:
            - containerPort: 8088

重要提示

我们将 Envoy 的日志级别设置为debug,仅供信息参考;此设置不应用于任何生产工作负载,因为它会生成非常大的日志,且在完成故障排除后应重置为info。所使用的镜像来自公共appmesh仓库,您可以通过gallery.ecr.aws/appmesh/aws-appmesh-envoy访问该仓库。

最后,我们创建基于 NLB 的服务,将 Envoy 代理暴露给互联网。这将使用基于 IP 的方案,并在负载均衡器上暴露端口80,该端口将通过目标组中 Pod 的 IP 地址映射到端口8088,从而将流量路由到每个 Envoy Pod:

---
apiVersion: v1
kind: Service
metadata:
  name: in-gw
  namespace: internet
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: external
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  ports:
    - port: 80
      targetPort: 8088
      protocol: TCP
  type: LoadBalancer
  loadBalancerClass: service.k8s.aws/nlb
  selector:
    app: in-gw

重要提示

虽然该服务将 Envoy 代理暴露到互联网,但我们也可以配置服务使用内部 NLB(如果是 HTTP/HTTPS 服务,则使用 ALB),这意味着其他非网格资源可以访问网格服务,但仅限于 AWS 网络或已连接的私有网络。

我们现在可以通过使用kubectl get svc命令获取我们刚刚创建的 NLB 的 URL,然后使用curl获取 K8s 服务 ID,来测试我们的myapp服务是否通过VirtualGateway暴露,使用以下命令:

$ kubectl get svc -n internet
NAME TYPE  CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
in-gw   LoadBalancer   10.100.12.98   k8s-internet-ingw-1122.elb.eu-central-1.amazonaws.com   80:30644/TCP   53m
$ curl -s http://k8s-internet-ingw-1122.elb.eu-central-1.amazonaws.com/id
{"id":"blue"}
$ curl -s http://k8s-internet-ingw-1122.elb.eu-central-1.amazonaws.com/id
{"id":"green"}

现在我们已经看到如何使用VirtualGateway资源将我们的 AWS App Mesh VirtualService暴露到互联网。接下来,我们将回顾如何使用 AWS Cloud Map,一个外部 DNS 服务,来执行与 AWS App Mesh 的服务发现。

使用 AWS Cloud Map 与 EKS

AWS Cloud Map 是一个云资源发现工具,因此,与 App Mesh 不同,它没有流量控制或可观察性功能。它仅允许消费者发现云服务(不仅仅是基于 EKS 的服务)。

Cloud Map 在命名空间中操作,因此我们要做的第一件事是创建一个名为myapp.prod.eu的新命名空间,稍后我们将使用它。我们可以使用以下 AWS CLI 命令来注册并验证是否已创建该命名空间:

$ aws servicediscovery create-private-dns-namespace --name prod.eu --description 'european production private DNS namespace' --vpc vpc-0614a71963e68bc86
{
    "OperationId": "pqrexzv7e5tn7wq64wiph6ztyb4c5ut3-5k7jsu2f"
}
$ aws servicediscovery get-operation  --operation-id pqrexzv7e5tn7wq64wiph6ztyb4c5ut3-5k7jsu2f
{
    "Operation": {
        "Status": "SUCCESS",
        "CreateDate": 1672566290.293,
        "Id": "pqrexzv7e5tn7wq64wiph6ztyb4c5ut3-5k7jsu2f",
        "UpdateDate": 1672566327.657,
        "Type": "CREATE_NAMESPACE",
        "Targets": {
            "NAMESPACE": "ns-pj3fxdidxmcgax7e"
        }
    }
}}

我们现在可以使用以下命令创建myapp服务:

$ aws servicediscovery create-service   --name myapp   --description 'Discovery service for the myapp service'   --namespace-id ns-pj3fxdidxmcgax7e   --dns-config 'RoutingPolicy=MULTIVALUE,DnsRecords=[{Type=A,TTL=300}]'   --health-check-custom-config FailureThreshold=1
{"Service": {
        "Description": "Discovery service for the myapp service",
        ……..
        "NamespaceId": "ns-pj3fxdidxmcgax7e",
        "Arn": "arn:aws:servicediscovery:eu-central-1:076637564853:service/srv-kc6c4f2mqt2buibx",
        "Name": "myapp"
    }

要注册我们的 Pods,我们现在需要调整VirtualNode定义,以使用 Cloud Map。在之前的blue-v1服务中,我们使用了 K8s DNS 名称,并且必须创建一个 K8s 服务来注册该域;请参阅以下代码片段:

serviceDiscovery:
    dns:
      hostname: blue-v1.blue.svc.cluster.local

我们现在可以调整此设置,以引用 Cloud Map 命名空间和服务,如以下代码片段所示,并重新部署虚拟节点:

serviceDiscovery:
    awsCloudMap:
      namespaceName: prod.eu
      serviceName: myapp

我们现在可以通过以下命令验证blue-v1 Pods 是否已将其 IP 注册到我们的 Cloud Map myapp服务中:

$ kubectl get po -n blue -o wide
NAME  READY   STATUS    RESTARTS   AGE     IP      …..
blue-v1-12   2/2     Running   0  7m35s   192.168.88.141   …
blue-v1-22   2/2     Running   0  7m35s   192.168.42.137   …
$ $ aws servicediscovery list-instances --service-id srv-kc6c4f2mqt2buibx
{
    "Instances": [
        {
            "Attributes": {
                "AWS_INSTANCE_IPV4": "192.168.42.137",
                "AWS_INIT_HEALTH_STATUS": "HEALTHY",
                ….},
        {
            "Attributes": {
                "AWS_INSTANCE_IPV4": "192.168.88.141",
                "AWS_INIT_HEALTH_STATUS": "HEALTHY",
                ….}]}

由于我们已将prod.eu命名空间连接到我们的 VPC,任何能够访问 VPC 解析器的节点也可以解析此名称,如以下来自其中一台 EC2 工作节点的示例所示:

sh-4.2$ dig myapp.prod.eu
<<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> myapp.prod.eu
………
;myapp.prod.eu.                 IN      A
;; ANSWER SECTION:
myapp.prod.eu.          300     IN      A       192.168.88.141
myapp.prod.eu.          300     IN      A       192.168.42.137
…..

重要提示

由于我们现在不再在 K8s 中使用 CoreDNS,因此任何引用VirtualNode的内容必须现在修改为使用 Cloud Map DNS 条目。这包括任何VirtualRouter和/或VirtualService

现在我们已经回顾了如何在 App Mesh 中使用 Cloud Map,让我们通过快速了解如何排查 Envoy 代理问题来结束本章。

排查 Envoy 代理问题

如您所见,Envoy 代理在 AWS App Mesh 中扮演着至关重要的角色。因此,能够排查它是一个关键技能。默认情况下,Envoy 日志记录设置为信息级别,而在调试时,第一步就是提高日志记录级别。

如果您能控制 Pod,那么您可以像我们在部署VirtualGateway时为myapp服务所做的那样,调整ENVOY_LOG_LEVEL变量,如以下清单片段所示:

          env:
          - name: ENVOY_LOG_LEVEL
            value: "debug"

注入到命名空间中的 Pod 会启用 Envoy 管理端口 9901,这样我们可以使用 kubectl port-forward 命令将本地端口映射到管理端口。以下命令是连接到 green 命名空间中 Pod 的示例:

$ kubectl port-forward -n green green-v1-cf45dcc99-fbdh7   8080:9901

我们将使用 Cloud9,这是一个集成的 AWS 开发环境,用于将 Web 浏览器连接到 Envoy Proxy 管理界面。以下截图显示了首页:

图 16.9 – Cloud9 IDE 中的 Envoy 管理首页

图 16.9 – Cloud9 IDE 中的 Envoy 管理首页

虽然首页上有很多有趣的数据,但我们希望更改日志级别,以便获得更详细的日志。我们可以通过刚刚设置的端口转发连接来做到这一点,使用以下命令,然后使用 kubectl logs 命令来 get–follow 日志内容:

$ curl -X POST http://localhost:8080/logging
active loggers:
  admin: info
  alternate_protocols_cache: info
$ curl -X POST http://localhost:8080/logging?level=debug
active loggers:
  admin: debug
  alternate_protocols_cache: debug
….
$ kubectl logs -n green green-v1-cf45dcc99-fbdh7 envoy

重要说明

请记得在前面的命令中指定 envoy 容器。

常见的 Envoy 代理问题包括以下内容:

  • 后端、VirtualGatewayVirtualRouter 路由配置不正确,导致 Envoy 代理无法看到 URL 请求

  • Envoy 没有 AWS 凭证,或者由于 VPC 网络问题无法连接到 AWS App Mesh 区域端点

  • 服务 DNS 解析未配置,既没有使用 K8s 虚拟服务,也没有配置外部 DNS

  • Envoy 无法连接到 App Mesh 控制平面以获取动态配置

envoy 日志详细级别较高,所以当你将其从信息级别更改为调试级别时,你将看到很多消息。以下表格描述了一些预期的消息,你可以通过这些消息来判断 envoy 是否正常工作。

重要说明

这不是一个详尽的列表,仅列出了你在使用 App Mesh 时常遇到的问题。

消息 描述
[2023-x][34][debug][router] [source/common/router/router.cc:470] [C1988][S225897133909764297] 集群 ‘cds_ingress_webapp_green-v1_green_http_8081’ 匹配 URL /id[2023-x][34][debug][router] [source/common/router/router.cc:673] [C1988][S225897133909764297] 路由解码头部:‘:authority’, ‘green-v1.green.svc.cluster.local:8081’‘:path’, /id 此消息显示 Envoy 正在接收到对 green-v1 服务端口 8081/id 路径的请求。
[2023-x][17][debug][config] [./source/common/config/grpc_stream.h:62] 正在建立新的 gRPC 双向流连接到 appmesh-envoy-management.eu-central-1.amazonaws.com:443,用于 rpc StreamAggregatedResources(stream .envoy.service.discovery.v3.DiscoveryRequest) 返回 (stream .envoy.service.discovery.v3.DiscoveryResponse); 此消息显示 Envoy 代理正在连接到 appmesh 区域端点。
2023-x][25][调试][aws] [source/extensions/common/aws/credentials_provider_impl.cc:161] 已获取 以下 AWS 凭证来自 EC2MetadataService: AWS_ACCESS_KEY_ID=****, AWS_SECRET_ACCESS_KEY=, AWS_SESSION_TOKEN= 该消息显示 Pod 获取其凭证以与 AWS API 进行交互。
[2023-x][17][调试][dns] [source/extensions/network/dns_resolver/cares/dns_impl.cc:275] green-v1.green.svc.cluster.local 的 DNS 解析已完成,状态为 0 此消息显示 Envoy 代理成功解析本地 K8s 服务的 DNS 名称。
[2023-x][1][调试] [AppNet Agent] Envoy 连接检查状态 200, {“stats”:[{“name”:”control_plane.connected_state”,”value”:1}]}[2023-01-01 12:21:35.455][1][调试] [AppNet Agent] 控制平面连接状态已更改为:已连接 这显示了 Envoy 代理正在连接到 appmesh 控制平面以获取动态配置。

表 14.1 – 有用的 Envoy 代理调试消息

现在让我们回顾一下本章的关键学习点。

概述

本章中,我们学习了什么是服务网格及其工作原理,然后探讨了 AWS App Mesh 的细节,并查看了它提供的一些服务。我们最初专注于如何使用一个简单的消费者和两个 Web 服务来管理东西向流量,并且在我们的示例中支持蓝绿部署。部署应用程序并使用原生 K8s 服务后,我们配置了服务网格,添加了 VirtualNodeVirtualService,使流量可以通过自动注入并配置到我们应用程序 Pod 中的 Envoy sidecar 容器进行管理。

然后我们使用 VirtualRouter 在绿色和蓝色服务之间进行负载均衡,这些服务代表同一服务的不同版本,支持蓝绿部署策略并最小化发布干扰。我们添加了 VirtualGateway,这使我们能够通过 NLB 和独立的 Envoy 代理将我们的应用暴露到 EKS 集群外部。

最后,我们探讨了如何将 AWS Cloud Map(一种外部 DNS 服务)集成到 App Mesh 中,以便在集群外进行服务发现,并且不再需要使用 K8s 假服务。我们还研究了如何通过增加日志级别来排查 Envoy 代理的故障,并查看了需要关注的常见问题和消息。现在,你应该能够描述 AWS App Mesh 的一些特性,并配置它与现有的 K8s 和 AWS CloudMap 配合使用。

在下一章中,我们将探讨如何使用 AWS 和第三方开源工具监控 EKS 集群。

深入阅读

第四部分:高级 EKS 服务网格与扩展

恭喜!现在你已经进入了掌握 EKS 之旅的最后阶段。在倒数第二部分,我们将介绍服务网格及其如何集成到 EKS 中。此外,我们还将进一步探讨高级实践,涵盖可观察性、监控以及工作负载、Pod 和节点组的扩展策略。最后,在本部分结束时,你将掌握自动化工具,并学习如何实施 CI/CD 实践,以简化你在 EKS 上的部署活动。

本节包含以下章节:

  • 第十七章EKS 可观察性

  • 第十八章扩展你的 EKS 集群

  • 第十九章在 EKS 上开发

第十七章:EKS 可观察性

在本书中,我们已经探讨了如何构建 EKS 集群和部署工作负载。然而,任何 EKS 部署的一个关键部分是可观察性。可观察性是指能够解读来自集群/工作负载的日志和指标,没有它,您无法排查/解决问题或理解容量或性能。可观察性还包括追踪,它允许您在请求穿越不同的 EKS 工作负载(微服务)时进行跟踪,从而简化分布式系统中的故障排除。

在本章中,我们将讨论您可以用来原生监控 AWS 上的集群和工作负载,或使用第三方工具进行监控的工具和技术。我们将涵盖以下主题:

  • 使用原生 AWS 工具监控集群和 Pod

  • 使用 Prometheus 和 Grafana 的托管服务构建仪表板

  • 使用 OpenTelemetry 进行追踪

  • 使用 DevOps Guru 进行机器学习

技术要求

您应当熟悉 YAML、AWS IAM 和 EKS 架构。在开始本章之前,请确保以下内容:

  • 您能够连接到 EKS 集群 API 端点

  • 您的工作站上已安装 AWS CLI、Docker 和 kubectl 二进制文件,并且您具有管理员权限

使用原生 AWS 工具监控集群和 Pod

AWS 部署 Kubernetes(EKS)相较于本地部署 Kubernetes 的一个主要优势在于它已预先集成到 CloudWatch 中,CloudWatch 是 AWS 的主要日志记录和监控平台。使用标准的 EKS 集群,您将自动获得控制平面日志、EC2 工作节点和负载均衡器(网络或应用负载均衡器)日志和指标,以及来自其他 AWS 服务的指标和日志,如数据库、消息队列等。

让我们来看看如何使用标准的 EC2 指标创建一个基本的 CloudWatch 仪表板,以了解我们集群的工作节点。

创建基本的 CloudWatch 仪表板

我们将使用 Terraform 创建一个简单的仪表板,显示所有带有特定集群名称标签的实例的聚合视图,并显示第二个仪表板,显示每个独立节点。接下来的代码片段展示了基本结构,一个 data 对象(用于检索当前所有 AWS 凭证数据)和一个 aws_cloudwatch_dashboard 对象,该对象包含两个其他对象(在下文中展示):

data "aws_caller_identity" "current" {}
resource "aws_cloudwatch_dashboard" "simple_Dashboard" {
  dashboard_name = "EKS-Dashboard"
  dashboard_body = <<EOF
{"widgets": [{widget1},{widget2}]}
EOF }

重要说明

您需要用接下来显示的实际代码替换 {widget1|2} 标记。

对于第一个小部件,我们将收集两个指标(CPU 和网络流量出)并基于节点组名称进行聚合,使用 eks:cluster-name 标签来选择属于我们 myipv4cluster 集群的节点:

{ "type": "explorer",
  "width": 24,
   "height": 2,
   "x": 0,
   "y": 0,
            "properties": {
                "metrics": [
                    { "metricName": "CPUUtilization",
                        "resourceType": "AWS::EC2::Instance",
                        "stat": "Average"
                    },
                    {. "metricName": "NetworkOut",
                        "resourceType": "AWS::EC2::Instance",
                        "stat": "Average"
                    }],
                "region":"eu-central-1",
                "aggregateBy": {
                    "key": "eks:nodegroup-name",
                    "func": "MAX"
                },
                "labels": [
                    {
                        "key": "eks:cluster-name",
                        "value": "myipv4cluster"
                    }
                ],
                "widgetOptions": {
                    "legend": {
                        "position": "bottom"
                    },
                    "view": "timeSeries",
                    "rowsPerPage": 1,
                    "widgetsPerRow": 2
                },
                "period": 60,
                "title": "Cluster EC2 Instances (aggregated)"
            }}

下图显示了 AWS 仪表板中的小部件,按节点组聚合了两个指标;ipv4mng 是该集群中唯一的节点组。

图 17.1 – 聚合的 CloudWatch 仪表板小部件

图 17.1 – 聚合的 CloudWatch 仪表板小部件

第二个小部件完全相同,但在小部件定义中没有包含 aggregateBy 键,因此生成以下可视化,它展示了相同的数据,但同时还显示了各个实例。

图 17.2 – CloudWatch 实例仪表盘小部件

图 17.2 – CloudWatch 实例仪表盘小部件

由于 AWS 管理 EKS 控制平面,默认情况下我们看不到 Kubernetes 控制平面指标。接下来,让我们看看如何查看控制平面指标并将其添加到仪表盘中。

查看控制平面日志

EKS 集群生成以下集群控制平面日志。每条日志对应 EKS 控制平面的一个特定组件:

  • 审计日志:这些日志包含一组记录,描述了用户或系统在使用 K8s API 时的操作。它们是了解集群发生了什么、何时发生以及谁导致这一事件的非常有价值的数据来源。

  • 身份验证日志:这些日志包含一组记录,描述了使用 IAM 凭证对用户和系统进行身份验证的过程,对于更详细地了解谁进行身份验证并使用集群非常有用。

  • API 服务器日志:这些日志包含一组记录,描述了不同组件使用的标志,对于理解集群如何被使用和配置非常有用。

  • 控制器日志:这些日志包含一组记录,描述了集群执行调度等操作时使用的控制循环,对于理解控制平面的运行非常有用。

  • 调度器日志:这些日志包含一组记录,描述了调度器部署、替换和删除 Pods 及 K8s 资源的操作,对于理解这个关键组件的工作原理非常有用。

更多详细信息可以在 K8s 文档的主要调试和日志记录部分找到,网址:kubernetes.io/docs/tasks/debug/

一种典型的最佳实践是为所有集群启用审计和身份验证日志,默认情况下,这些日志会发送到 CloudWatch 日志,可以用于调试、事件调查和取证。检查集群启用了哪些日志的最简单方法是使用 AWS 控制台,并浏览到 Amazon EKS 下的集群。以下是没有启用 API 日志的 日志记录 屏幕示例:

图 17.3 – 在 AWS 控制台中验证 EKS 集群日志

图 17.3 – 在 AWS 控制台中验证 EKS 集群日志

我们可以修改集群配置,通过以下命令启用审计和身份验证日志:

$ aws eks update-cluster-config --region eu-central-1 --name myipv4cluster --logging
'{"clusterLogging":[{"types":["audit","authenticator"],"enabled":true}]}'
{
    "update": {
        "status": "InProgress",
        "errors": [],
……
        "type": "LoggingUpdate",
        "id": "223148bb-8ec1-4e58-8b0e-b1c681c765a3",
        "createdAt": 1679304779.614}}

我们可以使用以下命令验证更新是否成功:

$ aws eks describe-update --region eu-central-1 --name myipv4cluster \
    --update-id 223148bb-8ec1-4e58-8b0e-b1c681c765a3
{
    "update": {
        "status": "Successful",
        "errors": [],
…..
        "type": "LoggingUpdate",
        "id": "223148bb-8ec1-4e58-8b0e-b1c681c765a3",
        "createdAt": 1679304779.614
    }}

如果现在查看 CloudWatch 服务,在 AWS 控制台中,我们将看到为我们的集群创建了一个新的日志组,我们可以将其用作查询和其他 CloudWatch 功能的数据源。以下是 myipv4cluster 日志组的示意图。

图 17.4 – CloudWatch 集群日志组

图 17.4 – CloudWatch 集群日志组

现在我们有了数据源,即 EKS 控制平面日志,我们可以使用 aws-auth ConfigMap 来控制访问 EKS 集群。我们还可以通过 日志 洞察 屏幕上的 添加到仪表板 按钮将其添加到我们的简单仪表板中:

图 17.5 – 使用 Log Insights 生成 EKS 审计数据的洞察

图 17.5 – 使用 Log Insights 生成 EKS 审计数据的洞察

重要说明

因为控制平面日志依赖 Amazon CloudWatch 日志来存储数据流、可视化和洞察,因此将会产生额外费用。欲了解更多详细信息,请参考 CloudWatch 定价页面:aws.amazon.com/cloudwatch/pricing/

默认情况下,CloudWatch 会无限期地保留你的日志,并且它们永远不会过期。这意味着它们不会被删除,除非你手动清理它们。如果 CloudWatch 日志组保留了你不想保存的旧控制平面日志,这可能会增加你的数据存储成本。

为了优化存储成本,有一个小窍门可以通过为每个 CloudWatch 日志组配置保留策略来节省开支。通过设置日志组的保留期限,任何超过保留设置的日志流将在该日志组中自动删除。你可以选择将 EKS 控制平面日志的保留期限设置为从 1 天到 10 年。

你可以按照以下步骤为 CloudWatch 日志组配置日志保留策略:

  1. 要查找你的集群的 CloudWatch 日志组,请进入 CloudWatch | 日志组 并搜索你的集群名称。

  2. 操作 下拉菜单中,选择 编辑保留设置(以下图为示例):

图 17.6 – 编辑日志组的保留设置

图 17.6 – 编辑日志组的保留设置

  1. 在下拉菜单中选择日志保留值并保存设置。一个典型的值是保留日志30 天/1 个月,然后删除它们,但这取决于你需要日志的用途,因为一些日志可能需要为合规或安全目的存储。

缩小旧的日志流可以确保只保留你真正关心的数据。这是另一个有用的技巧,可以帮助你降低在 Amazon CloudWatch 日志组中存储 EKS 控制平面日志的存储成本。

现在我们已经了解了 EKS 默认提供的功能,接下来我们将探讨如何增加一些内容以提升整体可观测性体验,从控制平面和 Pod 指标及日志开始。

探索控制平面和 Pod 指标

Kubernetes 控制平面组件在 /metrics 端点上以 Prometheus Metrics 格式公开指标。您可以使用 kubectl 或通过 抓取 /metrics 端点来访问这些指标。一个示例是使用 kubectl 提取节点数据,并使用 jq 工具来格式化数据并筛选到第一个(0)节点。在输出的最后,您可以看到 CPU 和 RAM 的节点 usage 数据:

$ kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" | jq '.items[0]'
{"metadata": {
    "name": "ip-1.2.3.4.eu-central-1.compute.internal",
    "creationTimestamp": "2023-03-18T15:23:51Z",
    "labels": {
      ….
      "failure-domain.beta.kubernetes.io/region": "..1",
    }
  },
  "timestamp": "2023-03-18T15:23:40Z",
  "window": "20.029s",
  "usage": {
    "cpu": "48636622n",
    "memory": "1562400Ki"
  }}

我们现在将 AWS /metrics 数据以及应用日志安装到 CloudWatch。这是通过安装两个代理程序来完成的,一个是 CloudWatch 代理(用于指标),另一个是 Fluent Bit 或 FluentD(用于日志)。我们将使用 QuickStart 指南,使用 Fluent Bit(这是一种更高效的代理程序)。

首先要做的是授予工作节点访问 CloudWatch API 的权限。我们将把 CloudWatchAgentServerPolicy 添加到工作节点的 IAM 策略中。下面显示了一个示例。这将允许任何 CloudWatch 代理或 FluentBit Pods 与 CloudWatch API 进行通信。

图 17.7 – CI 需要的额外权限

图 17.7 – CI 需要的额外权限

一旦我们应用了权限,就可以安装这两个代理程序(CW 和 FluentBit)。

我们现在可以使用此 URL 上显示的说明来安装代理程序:docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Container-Insights-setup-EKS-quickstart.html。接下来将显示配置和输出,我们已修改 ClusterNameRegionName 以匹配我们的集群配置。当我们执行时,CloudWatch 和 FluentBit 代理程序将被安装:

$ ClusterName=myipv4cluster
$ RegionName=eu-central-1
$ FluentBitHttpPort='2020'
$ FluentBitReadFromHead='Off'
$ [[ ${FluentBitReadFromHead} = 'On' ]] && FluentBitReadFromTail='Off'|| FluentBitReadFromTail='On'
[$ [ -z ${FluentBitHttpPort} ]] && FluentBitHttpServer='Off' || FluentBitHttpServer='On'
$ curl https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/quickstart/cwagent-fluent-bit-quickstart.yaml | sed 's/{{cluster_name}}/'${ClusterName}'/;s/{{region_name}}/'${RegionName}'/;s/{{http_server_toggle}}/"'${FluentBitHttpServer}'"/;s/{{http_server_port}}/"'${FluentBitHttpPort}'"/;s/{{read_from_head}}/"'${FluentBitReadFromHead}'"/;s/{{read_from_tail}}/"'${FluentBitReadFromTail}'"/' | kubectl apply -f –
% Total%Received%Xferd Average Speed Time Time Time  Current
100 16784  100 16784    0     0  95087      0 … 95363
…
daemonset.apps/fluent-bit created

我们可以使用以下命令验证代理程序的部署:

$ kubectl get po -n amazon-cloudwatch
NAME                     READY   STATUS    RESTARTS   AGE
cloudwatch-agent-6zl6c   1/1     Running   0          3m32s
cloudwatch-agent-xp45k   1/1     Running   0          3m32s
fluent-bit-vl29d         1/1     Running   0          3m32s
fluent-bit-vpswx         1/1     Running   0          3m32s

使用 FluentBit 进行日志记录

现在我们已经启动了代理程序,我们可以查看 CloudWatch 以查看正在生成的日志和指标。首先,如果我们查看 CloudWatch 日志并筛选 containerinsights,我们会看到四个新的日志组。下面显示了一个示例:

图 17.8 – CI 创建的新日志组

图 17.8 – CI 创建的新日志组

这些日志从节点日志中提取数据。下表显示了每个主机上使用的节点日志和日志文件。application 日志组包含写入 stdout 的容器日志。

/``aws/containerinsights/Cluster_Name/ application /``var/log/containers 中的所有日志文件
/``aws/containerinsights/Cluster_Name/ host /``var/log/dmesg/var/log/secure/var/log/messages 中的所有日志文件
/``aws/containerinsights/Cluster_Name/ dataplane /``var/log/journal 中的日志,针对 kubelet.servicekubeproxy.servicedocker.service
/``aws/containerinsights/Cluster_Name/ performance 包含 K8s 性能事件,详细信息请参见 docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Container-Insights-reference-performance-logs-EKS.html

表 17.1 – CI 日志组配置

我们使用以下清单创建一个生成日志消息然后自我终止的 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: logger
  namespace: logger-app
spec:
  containers:
  - image: busybox
    command: ["/bin/sh"]
    args: ["-c", "for i in `seq 4`; do echo 'Logging Message';done"]
    imagePullPolicy: IfNotPresent
    name: busybox
  restartPolicy: Never

我们可以使用以下命令创建命名空间、部署 Pod,并验证它是否生成日志消息:

$ kubectl create namespace logger-app
$ kubectl create -f logger.yaml
$ kubectl logs logger -n logger-app
Logging Message
Logging Message
Logging Message
Logging Message

如果现在查看 application 日志组,您将看到 logger Pod 的日志条目,该 Pod 位于 logger-app 命名空间中,并在您的某台主机上运行。在接下来的示例中,它是主机 192.168.32.216

图 17.9 – CloudWatch 容器日志输出

图 17.9 – CloudWatch 容器日志输出

从这一点开始,您可以像我们在 查看控制平面日志 部分中那样可视化您的日志。您还可以将日志转换为指标,并在我们之前创建的仪表板中进行可视化,或者将它们转发到其他日志服务,如 OpenSearch、Loki 或 Splunk。

使用 CloudWatch 的指标

CloudWatch CI 使用指标和日志的组合,通过 FluentBit 和 CloudWatch 代理的数据创建不同的视图和仪表板。最有用的视图之一是地图视图,它提供集群中部署资源的图形化视图,您可以在其上叠加 CPU 或内存热图。下面的示例展示了我们的集群,它显示了我们之前部署的 logger Pod,并且所有 CPU 状态均为绿色。

图 17.10 – CI 地图视图

图 17.10 – CI 地图视图

我们可以将视图从地图更改为性能仪表板,深入分析 CPU/RAM 等问题。以下示例显示了按命名空间划分的 CPU、RAM 等的详细信息(每一行):

图 17.11 – 每个命名空间的 CI 性能

图 17.11 – 每个命名空间的 CI 性能

重要提示

CloudWatch CI 是收费的,包括日志存储费用和 CloudWatch 自定义指标费用:aws.amazon.com/cloudwatch/pricing/

现在我们已经了解了如何使用原生 AWS 工具,接下来让我们看看如何使用一些标准的 K8s 第三方工具。

使用托管服务创建 Prometheus 和 Grafana 仪表板

Prometheus 和 Grafana 是 K8s 的事实标准监控工具。Prometheus 是一个从 /metrics 端点毕业的项目,可以生成告警并存储/转发数据。Grafana 是一个开源工具,可以可视化指标(时间序列)以及日志和追踪数据(在 使用 OpenTelemetry 进行追踪 部分讨论)。Prometheus 和 Grafana 一起提供与 CloudWatch 相当的功能,那么为什么要使用它们呢?

由于 Grafana 是开源的,它被广泛应用于社区,这意味着有很多为它创建的可重用仪表板,它集成了各种数据源(不仅仅是 AWS),并且可以说,它比 CloudWatch 拥有更完整的可视化功能。Prometheus 支持任何类型的健康检查端点,因此可以轻松用于您的应用程序以及一般的 Pod/集群度量。因此,Grafana 和 Prometheus 提供了一种灵活的解决方案,如果运行在 EKS 或 EC2 上,您只需要支付运行费用。这些工具的主要挑战是您需要管理基础设施,并且在某些情况下,需要使用额外的产品来管理度量或日志数据的长期存储。这正是AWS 托管 PrometheusAMP)和AWS 托管 GrafanaAMG)发挥作用的地方,它们提供了灵活性而无需管理的负担。

设置 AMP 和 AWS Distro for OpenTelemetry(ADOT)

我们要做的第一件事是创建一个 AMP 来捕获我们的度量数据;AWS 将创建一个区域来存储和查询度量(时间序列数据)。接下来显示的 Terraform 代码将创建一个myamp工作区:

resource "aws_prometheus_workspace" "myamp" {
  alias = "myamp"
}

以下 AWS SDK 命令可用于描述工作区并获取工作区的端点:

$ aws amp list-workspaces
{    "workspaces": [{..
            "alias": "myamp",
            "workspaceId": "ws-503025bc-01d5-463c-9157-11",
            "createdAt": 1679741187.4,
            "arn": "arn:aws:aps:eu-central-1:22:workspace/ws-503025bc-..}]
$ aws amp describe-workspace --workspace-id ws-503025bc-01d5-463c-9157-11
{..
        "prometheusEndpoint": "https://aps-workspaces.eu-central-1.amazonaws.com/workspaces/ws-503025bc-01d5-463c-9157-11/",
        "alias": "myamp",
        "workspaceId": "ws-503025bc-01d5-463c-9157-11",
        "arn": "arn:aws:aps:eu-central-1:22:workspace/ws-503025bc-01d5-463c-9157-11",
        "createdAt": 1679741187.4
}}}

AMP 可以从两个主要来源摄取度量数据,这些来源又“抓取”来自 Prometheus 启用的端点(例如 K8s 的/metrics端点)的度量数据。您可以使用现有的 Prometheus 服务器或ADOT来远程写入度量数据到 AMP 工作区。

我们将使用 ADOT,因为它稍后会用于追踪:

  1. 我们需要做的第一件事是创建prometheus命名空间来托管 ADOT,并创建一个带有 IAM 映射(IRSA)的服务账户。示例命令如下所示:

    $ kubectl create ns prometheus
    namespace/prometheus created
    $ eksctl create iamserviceaccount --name amp-iamproxy-ingest-role \
    --namespace prometheus --cluster myipv4cluster \
    --attach-policy-arn arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess \
    --approve --override-existing-serviceaccounts
    …..
    2023-03-25 16:02:19 created serviceaccount "prometheus/amp-iamproxy-ingest-role"
    
  2. 我们需要安装cert-manager(如果尚未安装),因为 ADOT 在操作过程中(如 sidecar 注入)将使用它。所需的命令如下所示:

    $ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.2
    …..
    validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
    $ kubectl get po -n cert-manager
    NAME            READY   STATUS    RESTARTS   AGE
    cert-manager-12-5k4kx    1/1    Running   0          47s
    cert-manager-cainjector-12-kj8w5   1/1  Running       47s
    cert-manager-webhook-12-bptdx      1/1  Running       47s
    
  3. 我们将把 ADOT 作为附加组件安装,以便简化升级和管理,因此现在需要授予 EKS 附加组件安装 ADOT 的权限,使用以下命令:

    $ kubectl apply -f https://amazon-eks.s3.amazonaws.com/docs/addons-otel-permissions.yaml
    namespace/opentelemetry-operator-system created
    clusterrole.rbac.authorization.k8s.io/eks:addon-manager-otel created
    clusterrolebinding.rbac.authorization.k8s.io/eks:addon-manager-otel created
    role.rbac.authorization.k8s.io/eks:addon-manager created
    rolebinding.rbac.authorization.k8s.io/eks:addon-manager created
    
  4. 我们将使用以下命令来部署和验证 ADOT 部署:

    $ CLUSTER_NAME=myipv4cluster
    $ aws eks create-addon --addon-name adot --addon-version v0.51.0-eksbuild.1 --cluster-name $CLUSTER_NAME
    {"addon": {
            "status": "CREATING",
    ..
            "createdAt": 1679761283.129}}
    $ aws eks describe-addon --addon-name adot --cluster-name $CLUSTER_NAME | jq .addon.status
    "ACTIVE"
    
  5. 我们现在可以使用以下命令下载并配置 ADOT 部署,修改REGIONprometheusEndpoint

    $ AMP_REMOTE_WRITE_URL=https://aps-workspaces.eu-central-1.amazonaws.com/workspaces/ws-503025bc-01d5-463c-9157-11/api/v1/remote_write
    $ AWS_REGION=eu-central-1
    $ curl -O https://raw.githubusercontent.com/aws-samples/one-observability-demo/main/PetAdoptions/cdk/pet_stack/resources/otel-collector-prometheus.yaml
      % Total    % Received % Xferd  Average Speed   …
    100 12480  100 12480    0     0  …
    $ sed -i -e s/AWS_REGION/$AWS_REGION/g otel-collector-prometheus.yaml
    $ sed -i -e s^AMP_WORKSPACE_URL^$AMP_REMOTE_WRITE_URL^g otel-collector-prometheus.yamls
    
  6. 最后,我们可以使用我们下载并修改的otel-collector-prometheus.yaml清单来安装并验证 ADOT,使用以下命令:

    $ kubectl apply -f ./otel-collector-prometheus.yaml
    opentelemetrycollector.opentelemetry.io/observability created
    clusterrole.rbac.authorization.k8s.io/otel-prometheus-role created
    clusterrolebinding.rbac.authorization.k8s.io/otel-prometheus-role-binding created
    $ kubectl get all -n prometheus
    NAME     READY   STATUS    RESTARTS   AGE
    pod/observability-collector-123   1/1     Running      110s
    NAME  TYPE  CLUSTER-IP  EXTERNAL-IP   PORT(S)    AGE
    service/observability-collector-monitoring   ClusterIP   10.100.154.149   <none>        8888/TCP   110s
    NAME   READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/observability-collector   1/1 110s
    NAME        DESIRED   CURRENT   READY   AGE
    replicaset.apps/observability-collector-123   1   1 110s
    
  7. 我们现在已将 ADOT 设置并配置为将度量数据发送到 AMP。我们可以通过 AMS Prometheus API 直接查询,或者通过 Grafana 查询。作为快速检查,我们可以安装并使用awscurl(它允许我们使用 AWS 凭证直接调用 API)来测试 AMP 是否接收到度量数据:

    $ pip3 install awscurl
    Defaulting to user installation because normal site-packages is not writeable
    Collecting awscurl
      Downloading awscurl-0.26-py3-none
    ….
    $ export AMP_QUERY_ENDPOINT=https://aps-workspaces.eu-central-1.amazonaws.com/workspaces/ws-503025bc-01d5-463c-9157-11/api/v1/query
    $ awscurl -X POST --region eu-central-1  --service aps "$AMP_QUERY_ENDPOINT?query=up"  | jq .
    {"status": "success",
      "data": {
        "resultType": "vector",
        "result": [
          {
            "metric": {
              "__name__": "up",
              "app": "cert-manager",
              "app_kubernetes_io_component": "controller",
              "app_kubernetes_io_instance": "cert-manager",
              "app_kubernetes_io_name": "cert-manager",
              "app_kubernetes_io_version": "v1.8.2",
              "instance": "192.168.68.6:9402",
              "job": "kubernetes-pods",
              "kubernetes_namespace": "cert-manager",
              "kubernetes_pod_name": "cert-manager-12-5k4kx",
              "pod_template_hash": "66b646d76"},
            "value": [
              1679763539.307,
              "1"]},
          {
            "metric": {
              "__name__": "up",
    …
           ]}]}}
    

重要提示

要访问 Prometheus API,无论是发送度量数据还是进行查询,我们需要通过 NAT 或互联网网关,或者使用 VPC 端点来访问公共的 AWS Prometheus API。

现在我们已经安装了 ADOT 并验证了指标已发送并存储在 Prometheus 中,我们可以看看如何通过 AMG 以图形化方式可视化这些指标。

设置 AMG 并创建仪表盘

AMG 需要有一个用户身份存储,这与 AWS IAM 不同。它将通过 AWS 身份中心(AWS Single Sign-On 的继任者)或通过 SAML 进行集成。安全断言标记语言SAML)是一种用于认证的开放标准,它将在两个方之间传输认证数据:身份提供者IdP)和服务提供者SP)。我们将使用AWS 身份中心AIC),因为它的设置更简单。通过 AWS 控制台,选择身份中心服务并点击启用按钮。接下来的控制台启动屏幕如下所示:

图 17.12 – AWS 身份中心启动屏幕

图 17.12 – AWS 身份中心启动屏幕

重要提示

您将被要求创建一个组织,用于对多个账户进行分组和管理。接受此操作后,您还将收到一封电子邮件,您需要验证该邮件,以便创建组织,并最终创建身份中心。

启用 AIC 后,您可以通过控制台点击添加用户按钮并输入一些基本信息,如姓名和电子邮件,来添加用户。在验证您的用户电子邮件并设置密码后,您将能够通过 AIC 控制台登录。

图 17.13 – 示例 AIC 用户管理员界面

图 17.13 – 示例 AIC 用户管理员界面

如果我们点击前面图中显示的用户,我们可以获得该用户的唯一身份标识符(用户 ID)。这在以下屏幕截图中显示为73847832-1031-70a6-d142-6fbb72a512f0。我们将在接下来的例子中配置 AMG 时使用这个 ID。

图 17.14 – AIC 用户详情

图 17.14 – AIC 用户详情

和 AMP 一样,AMG 是围绕工作区组织的。我们需要做的第一件事是为工作区设置 IAM 策略,以便能够与数据源(在我们的例子中是 AMP)进行通信。我们将创建一个 AMG 可以承担的角色,并将 Prometheus 权限附加到该角色。接下来的示例使用了 Terraform:

resource "aws_iam_role" "assume" {
  name = "grafana-assume"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      { Action = "sts:AssumeRole"
        Effect = "Allow"
        Sid    = ""
        Principal = {
          Service = "grafana.amazonaws.com"
        }
      },
    ]
  })
}
resource "aws_iam_policy" "policy" {
  name        = "amgadditional"
  path        = "/"
  description = "additional policies"
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "aps:ListWorkspaces",
          "aps:DescribeWorkspace",
          "aps:QueryMetrics",
          "aps:GetLabels",
          "aps:GetSeries",
          "aps:GetMetricMetadata"
        ]
        Effect   = "Allow"
        Resource = "*"
      },]})}
resource "aws_iam_role_policy_attachment" "amgattachement" {
  role       = aws_iam_role.assume.name
  policy_arn = aws_iam_policy.policy.arn
}

现在我们已经拥有了合适的 IAM 权限,允许 AMG 查询 Prometheus,我们可以创建工作区并将之前创建的用户(AIC)指定为管理员。我们将设置认证提供者为 AWS_SSO,并配置 Prometheus 和 CloudWatch 的引用。接下来的示例使用了 Terraform:

resource "aws_grafana_workspace" "myamg" {
  account_access_type      = "CURRENT_ACCOUNT"
  authentication_providers = ["AWS_SSO"]
  permission_type          = "SERVICE_MANAGED"
  role_arn                 = aws_iam_role.assume.arn
  data_sources              = ["PROMETHEUS","CLOUDWATCH"]
}
resource "aws_grafana_role_association" "admin" {
  role         = "ADMIN"
  user_ids     = ["73847832-1031-70a6-d142-6fbb72a512f0"]
  workspace_id = aws_grafana_workspace.myamg.id
}

重要提示

这可能需要几分钟来配置,您需要将 user_ids 更改为您在 AIC 中创建的用户 ID。

如果我们现在进入 AWS 中的 AMG 服务,我们将看到新的工作区,点击Grafana 工作区 URL下的链接将启动 Grafana 欢迎页面,我们可以使用在前面步骤中创建并关联的 AIC 凭据进行登录。接下来将展示一个示例:

图 17.15 – Grafana 工作区启动屏幕

图 17.15 – Grafana 工作区启动屏幕

虽然我们已经配置了 Grafana 服务以支持 Prometheus 和 CloudWatch,但我们需要在 Grafana 中配置数据源才能看到任何指标。第一步是点击左侧边栏中的 AWS 图标,然后点击数据源链接。接下来将展示一个示例:

图 17.16 – 选择 AWS 数据源

图 17.16 – 选择 AWS 数据源

接下来,选择我们在设置 AMP 和 ADOT章节中使用 Terraform 创建的myapm实例:

图 17.17 – 添加 AMP 数据源

图 17.17 – 添加 AMP 数据源

由于我们现在有了数据源,我们可以使用开源仪表板;我们将使用 Kubernetes 集群监控(通过 Prometheus),其 ID 为3119。如果点击 ID 中的3119,然后加载并保存仪表板,接下来将展示一个示例:

图 17.18 – 从 Grafana.com 导入开源仪表板

图 17.18 – 从 Grafana.com 导入开源仪表板

Grafana 的一个巨大优势是我们可以轻松地使用来自社区的工作成果。在接下来的示例中,我们刚刚导入的仪表板为集群提供了全面的视图,如果需要,我们甚至可以深入查看单个节点。

图 17.19 – Kubernetes 集群仪表板

图 17.19 – Kubernetes 集群仪表板

重要说明

请确保你已选择在本节中我们之前添加的 AMP 实例作为数据源。

现在我们已经了解了如何使用社区仪表板通过 AMG 可视化这些指标,接下来让我们看看如何使用 ADOT 发送应用程序跟踪信息。

使用 OpenTelemetry 进行跟踪

OpenTelemetry (OTel)是一个 CNCF 项目,提供了一种标准方式来发送应用程序跟踪信息。随着你开始构建分布式系统,跟踪变得非常重要,因为你需要能够追踪请求和响应在多个系统中的流转。OTel 提供了一个与供应商无关的工具库,可以用于将跟踪数据转发到后端进行可视化或分析。

在本书中,我们将使用 ADOT 作为跟踪转发器,AWS X-Ray 来分析和可视化跟踪数据。

修改我们的 ADOT 配置

由于 X-Ray 是 AWS 服务,我们首先需要修改服务账户权限,以允许它将跟踪数据发送到 X-Ray,因为当前它只有 AMP 权限。如果我们查看当前的ServiceAccount,可以得到 IAM 角色arn,如下所示:

$ kubectl get sa amp-iamproxy-ingest-role -n prometheus -o json
{
    "apiVersion": "v1",
    "kind": "ServiceAccount",
    "metadata": {
        "annotations": {
            "eks.amazonaws.com/role-arn": "arn:aws:iam::112233:role/eksctl-myipv4cluster-addon-iamserviceaccount-Role1-1V5TZL1L6J58X"
….
}

然后,我们可以直接将 X-Ray 权限添加到 IAM 角色中,如下图所示。我们将添加 AWSXRayDaemonWriteAccess 托管策略,以允许 ADOT 写入跟踪和段。

图 17.20 – 为 ADOT IRSA 添加 X-Ray 权限

图 17.20 – 为 ADOT IRSA 添加 X-Ray 权限

我们现在需要修改 ADOT 配置以支持 OTel 和 X-Ray。完整的配置可以在 raw.githubusercontent.com/aws-observability/aws-otel-community/master/sample-configs/operator/collector-config-xray.yaml 找到,新的接收器、处理器、出口器和管道已添加到 设置 AMP 和 ADOT 部分的 otel-collector-prometheus.yaml 文件中:

receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318
processors:
      batch/traces:
        timeout: 1s
        send_batch_size: 50
exporters:
      awsxray:
        region: eu-central-1
pipelines:
      traces:
        receivers: [otlp]
        processors: [batch/traces]
        exporters: [awsxray]

现在我们可以重新部署 ADOT 收集器,或者简单地删除它并重新创建,但如果我们查看日志,就可以看到新元素已经成功启动,我们将看到一个一切准备就绪的消息。接下来展示了命令和一些示例输出文本:

$ kubectl logs observability-collector-11 -n prometheus
2023/03/26 17:05:54 AWS OTel Collector version: v0.20.0
….
2023-03-26T17:05:54.973Z        info    pipelines/pipelines.go:82       Exporter started.       {"kind": "exporter", "data_type": "traces", "name": "awsxray"}
…
2023-03-26T17:05:54.973Z        info    pipelines/pipelines.go:82       Exporter started.
2023-03-26T17:05:54.974Z        info    pipelines/pipelines.go:102      Receiver is starting... {"kind": "receiver", "name": "otlp", "pipeline": "traces"}
2023-03-26T17:05:54.974Z        info    otlpreceiver/otlp.go:70 Starting GRPC server on endpoint 0.0.0.0:4317   {"kind": "receiver", "name": "otlp", "pipeline": "traces"}
2023-03-26T17:05:54.974Z        info    otlpreceiver/otlp.go:88 Starting HTTP server on endpoint 0.0.0.0:4318   {"kind": "receiver", "name": "otlp", "pipeline": "traces"}
…..
2023-03-26T17:05:54.975Z        info    service/collector.go:128        Everything is ready. Begin running and processing data.

对您的应用程序进行 OTel 跟踪仪表化可能是一个复杂的任务,因此它超出了本书的范围。相反,我们将使用 OTel 提供的一个简单的跟踪发射器来探索您可以使用 X-Ray 做些什么。

第一步是创建一个 K8s 命名空间来托管我们的发射器;接下来展示了一个示例:

$ kubectl create ns adot
namespace/adot created

接下来,我们将下载、修改并部署来自 raw.githubusercontent.com/aws-observability/aws-otel-community/master/sample-configs/sample-app.yaml 的示例应用程序(发射器)。您需要更改接下来展示的清单中的元素:

….
---
apiVersion: apps/v1
kind: Deployment
…
    spec:
      containers:
        - env:
            - name: AWS_REGION
              value: eu-central-1
            - name: LISTEN_ADDRESS
              value: 0.0.0.0:4567
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: http://observability-collector.prometheus:4317
            - name: OTEL_RESOURCE_ATTRIBUTES
              value: service.namespace=adot,service.name=emitter
          image: public.ecr.aws/aws-otel-test/aws-otel-java-spark:1.17.0
…..

一旦我们对 AWS_REGIONOTEL_EXPORTER_OTLP_ENDPOINTOTEL_RESOURCE_ATTRIBUTES 环境变量进行了修改,以指向我们修改过的 ADOT 实例,我们可以部署清单并使用接下来展示的命令验证它是否正在运行:

$ kubectl create -f sample-app-modified.yaml -n adot
service/sample-app created
deployment.apps/sample-app created
$ kubectl get po -n adot
NAME                         READY   STATUS    RESTARTS   AGE
sample-app-7cbb94b84-ckhdc   1/1     Running   0          8s

我们现在需要生成一些流量。幸运的是,OTel 还提供了一个流量生成器,它是一个与示例应用程序位于同一命名空间中的 Pod,并且会向应用程序 API 发出查询,后者又会向 amazon.com 发出调用。流量生成器 Pod 中使用的命令如下所示,完整的清单可以从 raw.githubusercontent.com/aws-observability/aws-otel-community/master/sample-configs/traffic-generator.yaml 下载:

….
- args:
            - /bin/bash
            - -c
            - sleep 10; while :; do curl sample-app:4567/outgoing-http-call > /dev/null 1>&1; sleep 2; curl ot-sample-app:4567/aws-sdk-call > /dev/null 2>&1; sleep 5; done

只要我们没有更改示例应用程序服务的名称,我们就可以保持不变运行。接下来展示的命令将部署流量生成器并开始向示例应用程序 API 发出调用:

$ kubectl create  -f traffic-generator.yaml -n adot
service/traffic-generator created
deployment.apps/traffic-generator created
$ kubectl get all  -n adot
NAME                  READY   STATUS    RESTARTS   AGE
pod/sample-app-7cbb94b84-ckhdc    1/1     Running   0    18m
pod/traffic-generator-123-ch4x2   1/1     Running   0    10m

如果我们现在进入 AWS 控制台中的 X-Ray 服务,我们将看到一个服务图,显示我们的客户端(流量生成器 Pod)正在调用 emitter 服务,而该服务又调用远程 aws.amazon.com 服务。服务图可以为健康状况提供遥测数据,并将延迟(latency)应用于其上,调整单个环的大小和颜色。服务图为您提供了一种非常便捷的方式来可视化您的服务。下图展示了我们的发射器/流量生成器部署的示例:

图 17.21 – X-Ray 服务图

图 17.21 – X-Ray 服务图

重要提示

由于客户端(流量生成器)代码没有与 OTel 集成,我们无法看到其延迟,因此我们的追踪从到达发射器服务时开始。

我们可以深入查看单个追踪,查看不同的段,以便故障排除或更详细地理解流量流动。在接下来的示例中,我们可以看到两个请求/响应——来自流量请求的初始请求,GET /outgoing-http-call,以及aws.amazon.com响应所花费的时间:

图 17.22 – X-Ray 追踪详情

图 17.22 – X-Ray 追踪详情

现在我们已经探讨了使用 OTel 和 X-Ray 的追踪,我们可以看看其中一个更高级的服务——DevOps Guru,它使用机器学习模型深入研究 EKS 节点级别的问题。

使用机器学习与 DevOps Guru

DevOps Guru 是一项完全托管的服务,利用预训练的机器学习模型来基准化资源,并深入了解其使用情况。由于它是完全托管的,您只需要设置并允许它运行。为此,选择 Amazon DevOps Guru 服务并点击 开始使用 按钮。我们将选择监控当前账户并分析所有资源的选项,并启用该服务。下一屏幕展示了所选择的选项:

图 17.23 – DevOps Guru 选项

图 17.23 – DevOps Guru 选项

我们还需要告诉 DevOps Guru 需要监控哪些资源,如下图所示:

图 17.24 – DevOps Guru 设置选项

图 17.24 – DevOps Guru 设置选项

重要提示

您可能需要等待 20 到 90 分钟,等待 DevOps Guru 收集并审核数据。

分析完成后,仪表盘将更新并显示任何发现。在接下来的示例中,您可以看到一个服务视图,显示我们的 EKS 集群是健康的。

生成资源问题很困难,但 DevOps Guru 会检测出具有高内存、CPU 或文件系统利用率的 EKS 主机,以及 Pod 级别的指标,如 CPU/RAM Pod 限制问题,识别出因资源耗尽而可能产生错误的资源。Amazon DevOps Guru 还跟踪容器重启、拉取镜像的问题或应用程序启动问题,有助于识别代码或清单配置不当。

Amazon DevOps Guru 的操作开销非常低,只需启用它并让其运行,而 AWS 还不断增强底层的机器学习模型,以提供更深入的洞察,但它是收费的,所以在使用之前请查看 aws.amazon.com/devops-guru/pricing/

图 17.25 – DevOps Guru 概要仪表盘

图 17.25 – DevOps Guru 概要仪表盘

在本节中,我们已经了解了 EKS 可观察性,以及如何使用各种 AWS 服务和开源工具更好地洞察集群、节点和应用程序。现在,我们将重新回顾本章的关键学习点。

总结

在本章中,我们探讨了收集和分析 EKS 日志、指标和跟踪的不同方式,这通常被称为可观察性。我们首先了解了如何安装日志和指标代理(分别为 fluentBit 和 CloudWatch),这些代理可以轻松集成 AWS CloudWatch 服务和 Container Insights,以提供对数据的详细分析,而无需部署任何监控服务器或软件许可证。

虽然 CloudWatch 提供了一个完整的监控平台,我们还讨论了有些人希望使用开源或非 AWS 服务,以获得更大的灵活性并减少平台锁定。Prometheus 和 Grafana 是开源项目,提供类似于 CloudWatch 的功能,并且有着一个庞大的社区支持,但需要安装和管理。

接下来,我们回顾了如何部署和配置 AMP 和 AMG,以获得这些服务的灵活性,但又没有操作开销,以及如何将 ADOT 部署到集群中,转发 K8s /metrics 数据到 AMP。我们还从 Grafana.com 部署了一个社区开发的 K8s 监控仪表盘,能够可视化标准的 K8s 指标,展示了如何轻松构建复杂的可视化。

接着,我们扩展了 ADOT 配置,支持收集 OTel 跟踪信息并将其转发到 AWS X-Ray 服务。我们在 EKS 中部署了一个简单的跟踪发射器和流量生成服务,并查看了 X-Ray 中的服务图和段信息,以帮助我们从流量量、延迟和错误的角度了解小型微服务架构中的流量流动。

最后,我们启用了 Amazon DevOps Guru 来提供更好的分析,且在集群和集群中的节点上零操作开销。

在下一章中,我们将探讨如何通过集群扩展工具和方法提高系统的弹性和性能。

进一步阅读

第十八章:扩展你的 EKS 集群

在 EKS(以及 K8s)上进行容量规划可能很困难!如果你低估或高估了集群资源,可能无法满足应用程序的需求,或者最终支付的费用比实际需要的要多。困难的原因之一是,很难准确预测应用程序的预期负载。例如,对于一个 Web 应用程序,负载通常是非确定性的,一次成功的营销活动或类似 Amazon Prime Day 的事件可能会使负载增加三倍或四倍。因此,需要某种形式的集群/Pod 扩展策略来应对现代应用程序负载的峰值和低谷。

在本章中,我们将介绍几种常见的策略和工具,这些策略和工具可以与 Amazon EKS 一起使用,并帮助你理解如何优化 EKS 集群以应对负载和成本。具体来说,本章涵盖以下主题:

  • 理解 EKS 中的扩展

  • 使用集群自动扩展器扩展 EC2 ASG

  • 使用 Karpenter 扩展工作节点

  • 使用水平 Pod 自动扩展器扩展应用程序

  • 使用自定义指标扩展应用程序

  • 使用 KEDA 扩展

技术要求

读者应当熟悉 YAML、AWS IAM 和 EKS 架构。在开始本章之前,请确保以下几点:

  • 你已连接到 EKS 集群的 API 端点

  • 已在你的工作站上安装 AWS CLI、Docker 和 kubectl 二进制文件,并具有管理员权限

理解 EKS 中的扩展

当我们考虑扩展任何系统或集群时,我们通常从两个维度来思考:

  • 增加系统或实例的大小,称为垂直扩展

  • 增加系统或实例的数量,称为水平扩展

以下图示说明了这些选项。

图 18.1 – 一般的扩展策略

图 18.1 – 一般的扩展策略

扩展策略与弹性模型密切相关,其中你有一个传统的主备或 N+1 弹性架构,比如关系型数据库。然后,当你增加容量时,通常需要通过增加数据库实例的大小来进行垂直扩展。这是因为系统架构的限制。

在 K8s 中,弹性模型基于多个工作节点托管多个 Pod,通过入口负载均衡器提供一致的入口点。这意味着节点故障应该对系统影响较小。因此,扩展策略主要是水平扩展(向外扩展),虽然你也可以通过垂直扩展 Pods 来应对需求增加,但我们将重点关注水平扩展。

由于 AWS 管理 EKS 控制平面(扩展性和弹性),我们将主要关注数据平面(工作节点)和应用程序资源(Pod/容器)。让我们从高层次探讨支持 EKS 数据平面扩展的技术开始。

EKS 扩展技术

支持 EKS 扩展的技术涉及三个层次:

  1. 支持数据平面扩展的 AWS 技术,如 EC2 自动扩展组ASGs)和允许系统与这些 ASGs 交互的 AWS API。

  2. 支持扩展和部署 Pods 的 K8s 对象(类型),例如 Deployments。

  3. K8s 调度器和控制器提供了 K8s 对象(2)与 AWS 技术(1)之间的连接,支持在 Pod 和集群级别进行水平扩展。

以下图表展示了这三层技术。

图 18.2 – EKS 扩展技术

图 18.2 – EKS 扩展技术

让我们详细看看这些层次。

AWS 技术

第八章**,《管理 EKS 上的工作节点》中,我们讨论了使用 EC2 ASG 提高系统弹性的方式。当你创建一个 ASG 时,需要指定该组中 EC2 实例的最小数量、最大数量以及期望数量,组会在这些限制范围内进行扩展。调用 EC2 API 可以基于这些调用实现组的扩展和收缩(在一组冷却限制内)。调用 EC2 ASG API 的实体必须做出扩展和收缩的决策。

第十五章**,《与 AWS Fargate 配合使用》中,我们讨论了如何使用 Fargate 配置文件在 Fargate 实例上创建 Pods。在这种情况下,AWS 负责 Pods 的部署和放置,而 Fargate 服务负责扩展决策,调用方只需请求部署一个或多个 Pods。

K8s 对象

在本书中,我们使用 K8s 部署来部署和扩展我们的 Pods。实际上,这使用了 deployments,它又使用 ReplicaSets 根据需要添加/移除新的 Pods,并支持诸如滚动更新等部署策略。K8s 还支持 StatefulSets 和 DaemonSets 进行 Pod 部署。

StatefulSet 是一个 K8s 控制器,用于部署 Pods,但它会保证特定的部署顺序,并且 Pod 名称是唯一的,同时还提供存储。DaemonSet 也是一个控制器,确保 Pod 在集群的所有节点上运行。

ReplicaSets 和 StatefulSets 都创建 Pods,期望 K8s 调度器能够部署它们。如果调度器判断没有足够的资源——通常是工作节点的 CPU/RAM 或网络端口(NodePort 服务)——则 Pod 会保持在 Pending 状态。

K8s 调度器和控制器

K8s 被设计为可扩展的,并且可以通过控制器进行扩展。通过自动扩展,我们可以使用以下列表中的控制器扩展 EKS,稍后我们将详细探讨这些控制器:

  • K8s 集群自动扩展器CA)可以根据 K8s 调度器的需求自动扩展 AWS ASGs。

  • Karpenter 可以根据 K8s 调度器的需求自动扩展 EC2 实例

  • K8s 水平 Pod 自动扩展器 (HPA) 可以根据自定义指标或 EC2 或 Fargate 实例的 CPU 利用率扩展部署

  • 使用 KEDA 集成不同的事件源,通过 HPA 控制触发扩展操作

现在我们已经了解了三个技术层次,接下来深入探讨如何安装和配置不同的控制器和服务,以扩展我们的 EKS 集群。

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

Kubernetes CA 是 K8s 生态系统的核心部分,用于基于两个主要条件扩展工作节点的进出:

  • 如果 Kubernetes 集群中有一个 Pod 因为资源不足错误而处于 Pending 状态

  • 如果 Kubernetes 集群中的工作节点被 Kubernetes CA 识别为低效使用

下图说明了支持将单个 Pod 放置在 Pending 状态并且未被调度的扩展操作的基本流程。

图 18.3 – 高级集群自动扩展器流程

图 18.3 – 高级集群自动扩展器流程

在上面的图示中,我们可以看到以下内容:

  1. CA 正在积极寻找因资源不足而无法调度的 Pod,并处于 Pending 状态。

  2. CA 调用 EC2 ASG API 来增加所需的容量,从而在 ASG 中添加一个新节点。需要注意的一点是,节点需要被标记为 k8s.io/cluster-autoscaler/,以便 CA 能够发现这些节点及其实例类型。

  3. 一旦节点已注册到集群,调度器将把 Pod 调度到该节点。

  4. 一旦 Pod 被部署,并且假设 Pod 本身没有问题,状态将变更为 Running

现在我们已经了解了 CA 背后的概念,接下来让我们安装它。

在您的 EKS 集群中安装 CA

由于 CA 将与 AWS EC2 API 交互,首先我们需要确保自动扩展器使用的子网已经正确标记。如果您使用的是 k8s.io/cluster-autoscaler/enabledk8s.io/cluster-autoscaler/myipv4cluster,并已应用于子网,这应该是自动完成的:

$ aws ec2 describe-subnets --filters "Name=tag:k8s.io/cluster-autoscaler/enabled,Values=true" | jq -r '.Subnets[].SubnetId'
subnet-05d5323d274c6d67e
subnet-087e0a21855f08fd3
subnet-0dbed7d2f514d8897
$ aws ec2 describe-subnets --filters "Name=tag:k8s.io/cluster-autoscaler/myipv4cluster,Values=owned" | jq -r '.Subnets[].SubnetId'
subnet-05d5323d274c6d67e
subnet-087e0a21855f08fd3
subnet-0dbed7d2f514d8897

注意

myipv4cluster 是我示例中集群的名称,但您的可能不同。

现在我们已经确认子网已应用正确的标签,可以设置 AWS IAM 策略,并将其与 K8s 服务帐户关联,该服务帐户将由 CA Pods 使用。

注意

最佳实践是添加条件,将策略限制为仅限于它所部署的集群拥有的资源。在我们的案例中,我们将使用前一步中创建/验证的标签。

以下是建议您创建的自动扩展策略:

{"Version": "2012-10-17",
    "Statement": [ {
            "Effect": "Allow",
            "Action": [
                "autoscaling:SetDesiredCapacity",
                "autoscaling:TerminateInstanceInAutoScalingGroup"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "autoscaling:ResourceTag/k8s.io/cluster-autoscaler/enabled": "true",
                    "aws:ResourceTag/k8s.io/cluster-autoscaler/myipv4cluster": "owned"
                }}},
        { "Effect": "Allow",
            "Action": [
                "autoscaling:DescribeAutoScalingInstances",
                "autoscaling:DescribeAutoScalingGroups",
                "autoscaling:DescribeScalingActivities",
                "ec2:DescribeLaunchTemplateVersions",
                "autoscaling:DescribeTags",
                "autoscaling:DescribeLaunchConfigurations",
                "ec2:DescribeInstanceTypes"
            ],
            "Resource": "*"
        }]}

我们可以使用以下命令创建策略,并将其与 EKS 服务帐户关联。此外,请注意当前的 EKS 集群版本:

$ aws iam create-policy --policy-name AmazonEKSClusterAutoscalerPolicy \
--policy-document file://autoscaler_policy.json
{
    "Policy": {
        "…….}}
$ eksctl create iamserviceaccount  --cluster=myipv4cluster --namespace=kube-system --name=cluster-autoscaler  --attach-policy-arn=arn:aws:iam::112233:policy/AmazonEKSClusterAutoscalerPolicy --override-existing-serviceaccounts --approve
……
2023-05-02 19:31:55 []  created serviceaccount "kube-system/cluster-autoscaler"
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"22+", GitVersion:"v1.22.6-eks-7d68063", GitCommit:"f24e667e49fb137336f7b064dba897beed639bad", GitTreeState:"clean", BuildDate:"2022-02-23T19:32:14Z", GoVersion:"go1.16.12", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"22+", GitVersion:"v1.22.17-eks-ec5523e", GitCommit:"49675beb7b1c90389418d067d37024616a313555", GitTreeState:"clean", BuildDate:"2023-03-20T18:44:58Z", GoVersion:"go1.16.15", Compiler:"gc", Platform:"linux/amd64"}

你现在可以下载raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/aws/examples/cluster-autoscaler-autodiscover.yaml安装清单,并修改以下几行(行号可能会有所不同)。

修改自动发现标签(第 165 行)

这将配置自动伸缩器使用我们(或eksctl)创建的特定集群标签:

node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/myipv4cluster

在自动发现标签行(第 165 行)下添加命令开关

这些开关使自动伸缩器能够更有效地跨可用区进行平衡,并将自动伸缩节点组缩放到零:

- --balance-similar-node-groups
- --skip-nodes-with-system-pods=false

修改容器镜像以与 K8s 服务器版本对齐(第 149 行)

这将使用与集群本身相同的自动伸缩器版本。虽然有更新版本,但最好使用与集群相同的版本:

image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.22.2

注意

可以使用github.com/kubernetes/autoscaler/releases页面查找所需的发布版本。

现在我们有了修改后的清单,可以使用以下命令部署自动伸缩器、修补服务帐户并验证其是否在运行:

$ kubectl create -f cluster-autoscaler-autodiscover.yaml
….
deployment.apps/cluster-autoscaler created
Error from server (AlreadyExists): error when creating "cluster-autoscaler-autodiscover.yaml": serviceaccounts "cluster-autoscaler" already exists
$ kubectl patch deployment cluster-autoscaler -n kube-system -p '{"spec":{"template":{"metadata":{"annotations":{"cluster-autoscaler.kubernetes.io/safe-to-evict": "false"}}}}}'
$ kubectl get po -n kube-system
NAME                  READY   STATUS    RESTARTS   AGE
aws-node-2wrq6         1/1     Running   0          3d7h
aws-node-blfl2         1/1     Running   0          3d7h
cluster-autoscaler-577 1/1     Running   0          12s
……

注意

部署返回错误,因为服务帐户已存在。这不会影响自动伸缩器 Pod 的部署或运行,但意味着如果删除清单,SA 也会被删除。

现在我们已经安装了 CA,接下来测试它是否正常工作。

测试集群自动伸缩器

如果我们查看eksctl为与集群一起创建的节点组创建的 ASG,我们可以看到所需、最小和最大容量已设置为5

图 18.4 – eksctl 节点组 ASG 容量

图 18.4 – eksctl 节点组 ASG 容量

如果我们查看集群,可以看到我们已经部署并注册了两个节点。改变 ASG 的最大容量对当前节点没有影响,因为尚未触发任何重新伸缩:

$ kubectl get node
NAME            STATUS   ROLES    AGE     VERSION
ip-192-168-18-136.eu-central-1.compute.internal   Ready    <none>   3d8h    v1.22.17-eks-a59e1f0
ip-192-168-43-219.eu-central-1.compute.internal   Ready    <none>   3d8h    v1.22.17-eks-a59e1f0

如果我们现在部署一个包含大量副本的清单,我们将看到自动伸缩器将 ASG 扩展以支持 Pods。以下清单将请求 150 个副本/Pods,并导致更多节点被配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 150
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

如果我们部署这个清单并使用以下命令监控节点和自动伸缩器,我们可以看到 Pods 从Pending状态过渡到Running状态,因为 ASG 在伸缩:

$ kubectl create -f tester.yaml
$ kubectl get po
NAME       READY   STATUS    RESTARTS   AGE
nginx-deployment-12   0/1     Pending   0          18m
nginx-deployment-13   0/1     Pending   0          18m
….
WAIT 10 MINUTES
$ kubectl get node
NAME          STATUS   ROLES    AGE    VERSION
ip-192-168-18-136.eu-central-1.compute.internal   Ready    <none>   3d8h   v1.22.17-eks-a59e1f0
ip-192-168-34-4.eu-central-1.compute.internal     Ready    <none>   18m    v1.22.17-eks-a59e1f0
ip-192-168-43-219.eu-central-1.compute.internal   Ready    <none>   3d8h   v1.22.17-eks-a59e1f0
ip-192-168-77-75.eu-central-1.compute.internal    Ready    <none>   18m    v1.22.17-eks-a59e1f0
ip-192-168-85-131.eu-central-1.compute.internal   Ready    <none>   18m    v1.22.17-eks-a59e1f0

我们可以看到节点已经扩展到 5(ASG 配置中设置的最大值),以支持增加的 pod 数量,但一些 pod 仍然会保持 Pending 状态,因为没有足够的节点来支持它们,并且自动扩展器不会违反 ASG 容量限制来创建更多节点。如果我们现在删除清单并使用以下命令,我们可以看到 ASG 在节点至少没有需要 10 分钟后(这是默认设置——您可以调整 CA 的扫描时间,但这会增加集群的负载,正如我们稍后将看到的,Karpenter 更适合快速扩展)后会缩减回 2(期望容量)。

$ kubectl delete -f tester.yaml
deployment.apps "nginx-deployment" deleted
WAIT AT LEAST 10 MINUTES
$ kubectl get node
NAME             STATUS   ROLES    AGE    VERSION
ip-192-168-34-4.eu-central-1.compute.internal    Ready    <none>   47m   v1.22.17-eks-a59e1f0
ip-192-168-77-75.eu-central-1.compute.internal   Ready    <none>   47m   v1.22.17-eks-a59e1f0

注意

原来的旧节点已被移除,剩下了两个新的节点。

如果您想监控自动扩展器的操作,可以使用以下命令:

$ kubectl -n kube-system logs -f deployment.apps/cluster-autoscaler
I0502 21:22:59.599092       1 scale_down.go:829] ip-192-168-18-136.eu-central-1.compute.internal was unneeded for 7m2.220392583s

使用 ASG 有其优点,但当你想要使用多种不同的实例类型或快速扩展/缩减时,Karpenter 可能提供更多的灵活性。让我们看看如何部署和使用 Karpenter。

使用 Karpenter 扩展工作节点

Karpenter (karpenter.sh) 是一个为 Kubernetes 构建的开源自动扩展解决方案,旨在在不需要节点或 ASG 的情况下,根据需求波动来构建和移除容量。下图展示了整体流程:

图 18.5 – 高级 Karpenter 流程

图 18.5 – 高级 Karpenter 流程

在前面的图中,我们可以看到以下内容:

  1. Karpenter 正在积极寻找那些由于资源不足而无法调度的处于 Pending 状态的 pod。

  2. Karpenter 控制器将检查这些待处理 pod 的 资源 请求节点选择器亲和性容忍度,并为满足 pod 要求的节点进行配置。

  3. 一旦节点已注册到集群,调度器将会在该节点上调度 pod。

  4. 一旦 pod 部署完成,并且假设 pod 本身没有问题,状态将更改为 Running

现在我们已经了解了 Karpenter 背后的概念,让我们安装并进行测试。

在您的 EKS 集群中安装 Karpenter

我们将在现有的托管节点组(ASG)上创建 Karpenter 控制器,但用它来调度工作负载到额外的工作节点上:

  1. 为了确保我们的安装成功,我们首先需要设置一些环境变量。我们可以按如下方式进行:

    $ export CLUSTER_NAME=myipv4cluster
     export AWS_PARTITION="aws"
     export AWS_REGION="$(aws configure list | grep region | tr -s " " | cut -d" " -f3)"
     export OIDC_ENDPOINT="$(aws eks describe-cluster --name ${CLUSTER_NAME} --query "cluster.identity.oidc.issuer" --output text)"
     export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
    $ printenv
    …..
    AWS_PARTITION=aws
    CLUSTER_NAME=myipv4cluster
    AWS_REGION=eu-central-1
    AWS_ACCOUNT_ID=111222333
    OIDC_ENDPOINT=https://oidc.eks.eu-central-1.amazonaws.com/id/123455
    

注意

修改集群名称以与您的集群对齐。

  1. 接下来,我们需要创建一个 AWS IAM 策略,关联到 Karpenter 创建的节点上。这将确保它们拥有正确的 EKS、ECR 和 SSM 权限。我们从一个允许 EC2 实例假设角色的信任策略开始,如下所示:

    { "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "",
          "Effect": "Allow",
          "Principal": {
            "Service": "ec2.amazonaws.com"
          },
          "Action": "sts:AssumeRole"
        }]}
    
  2. 然后我们可以创建一个角色 KarpenterInstanceNodeRole,该角色由 Karpenter 创建的节点使用,并引用如下所示的信任策略:

    $ aws iam create-role --role-name KarpenterInstanceNodeRole     --assume-role-policy-document file://"trust_policy.json"
    
  3. 然后,我们可以使用以下命令将四个必需的托管策略附加到该角色:

    $ aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy --role-name KarpenterInstanceNodeRole
    $ aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy --role-name KarpenterInstanceNodeRole
    $ aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly --role-name KarpenterInstanceNodeRole
    $ aws iam attach-role-policy --policy-arn  arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore --role-name KarpenterInstanceNodeRole
    
  4. 我们现在创建用于节点的实例配置文件:

    $ aws iam create-instance-profile --instance-profile-name "KarpenterNodeInstanceProfile-${CLUSTER_NAME}"
    {
        "InstanceProfile": {
            "Path": "/",
            "InstanceProfileName": "KarpenterNodeInstanceProfile-myipv4cluster",
            "InstanceProfileId": "AIPARDV7UN62ZBGEB7AV4",
            "Arn": "arn:aws:iam::111222333:instance-profile/KarpenterNodeInstanceProfile-myipv4cluster",
            "CreateDate": "2023-05-03T06:55:12+00:00",
            "Roles": []
        }
    }
    
  5. 然后,我们将之前创建的角色分配给实例配置文件:

    $ aws iam add-role-to-instance-profile --instance-profile-name "KarpenterNodeInstanceProfile-${CLUSTER_NAME}" --role-name "KarpenterInstanceNodeRole"
    
  6. 现在我们已经有了 EC2 节点使用的实例配置文件。接下来的任务是创建与 Karpenter pod 关联的 AWS IAM 策略。我们将经历与实例配置文件相似的过程。首先,我们创建信任策略,允许集群的 OIDC 端点承担角色。这将使用我们之前设置的 OIDC_ENDPOINTAWS_ACCOUNT 环境变量:

    $ cat << EOF > controller-trust-policy.json
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_ENDPOINT#*//}"
                },
                "Action": "sts:AssumeRoleWithWebIdentity",
                "Condition": {
                    "StringEquals": {
                        "${OIDC_ENDPOINT#*//}:aud": "sts.amazonaws.com",
                        "${OIDC_ENDPOINT#*//}:sub": "system:serviceaccount:karpenter:karpenter"
                    }
                }
            }
        ]
    }
    EOF
    
  7. 然后,我们使用以下命令,通过信任策略为 Karpenter 控制器创建角色:

    $ aws iam create-role --role-name KarpenterControllerRole-${CLUSTER_NAME} --assume-role-policy-document file://controller-trust-policy.json
    {"Role": {
            "Path": "/",
            "RoleName": "KarpenterControllerRole-myipv4cluster",
    …..
    
  8. 以下命令用于创建控制器所需的策略,并再次依赖我们之前设置的环境变量:

    cat << EOF > controller-policy.json
    {
        "Statement": [
            {
                "Action": [
                    "ssm:GetParameter",
                    "ec2:DescribeImages",
                    "ec2:RunInstances",
                    "ec2:DescribeSubnets",
                    "ec2:DescribeSecurityGroups",
                    "ec2:DescribeLaunchTemplates",
                    "ec2:DescribeInstances",
                    "ec2:DescribeInstanceTypes",
                    "ec2:DescribeInstanceTypeOfferings",
                    "ec2:DescribeAvailabilityZones",
                    "ec2:DeleteLaunchTemplate",
                    "ec2:CreateTags",
                    "ec2:CreateLaunchTemplate",
                    "ec2:CreateFleet",
                    "ec2:DescribeSpotPriceHistory",
                    "pricing:GetProducts"
                ],
                "Effect": "Allow",
                "Resource": "*",
                "Sid": "Karpenter"
            },
            {
                "Action": "ec2:TerminateInstances",
                "Condition": {
                    "StringLike": {
                        "ec2:ResourceTag/karpenter.sh/provisioner-name": "*"
                    }
                },
                "Effect": "Allow",
                "Resource": "*",
                "Sid": "ConditionalEC2Termination"
            },
            {
                "Effect": "Allow",
                "Action": "iam:PassRole",
                "Resource": "arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/KarpenterInstanceNodeRole",
                "Sid": "PassNodeIAMRole"
            },
            {
                "Effect": "Allow",
                "Action": "eks:DescribeCluster",
                "Resource": "arn:${AWS_PARTITION}:eks:${AWS_REGION}:${AWS_ACCOUNT_ID}:cluster/${CLUSTER_NAME}",
                "Sid": "EKSClusterEndpointLookup"
            }
        ],
        "Version": "2012-10-17"
    }
    EOF
    
  9. 我们使用在上一步骤中创建的策略来创建将由 Karpenter 控制器使用的 IAM 角色:

    $ aws iam put-role-policy --role-name KarpenterControllerRole-${CLUSTER_NAME} --policy-name KarpenterControllerPolicy-${CLUSTER_NAME} --policy-document file://controller-policy.json
    
  10. 我们现在需要为希望 Karpenter 在添加节点时使用的子网添加标签 karpenter.sh/discovery=myipv4cluster。我们可以使用 describe-subnets 命令来识别哪些子网已经被标记。以下是使用示例:

    $ aws ec2 describe-subnets --filters "Name=tag:karpenter.sh
    /discovery,Values=myipv4cluster" | jq -r '.Subnets[].SubnetId'
    subnet-05d5323d274c6d67e
    subnet-087e0a21855f08fd3
    subnet-0dbed7d2f514d8897
    

注意

我们使用了与自动扩展器相同的子网,但您可以使用不同的子网。

  1. 我们还需要使用相同的发现标签 karpenter.sh/discovery=myipv4cluster 来标记安全组,以便 Karpenter 节点能够与控制平面和其他节点通信。我们将使用集群的安全组,可以在 网络 部分找到:

图 18.6 – 定位集群安全组

图 18.6 – 定位集群安全组

我们可以使用以下命令来标记安全组:

$ aws ec2 create-tags --tags "Key=karpenter.sh/discovery,Value=${CLUSTER_NAME}" --resources sg-0222247264816d807
$ aws ec2 describe-security-groups --filters "Name=tag:karpenter.sh/discovery,Values=myipv4cluster" | jq -r '.SecurityGroups[].GroupId'
sg-0222247264816d807
  1. 最后,我们需要编辑 aws-auth ConfigMap 以允许 Karpenter 节点角色与 API 服务器进行身份验证。我们需要向 ConfigMap 添加以下配置的新组:

    mapRoles: |
        - groups:
          - system:bootstrappers
          - system:nodes
          rolearn: arn:aws:iam::123:role/eksctl-myipv4cluster-node
          username: system:node:{{EC2PrivateDNSName}}
        - groups:
          - system:bootstrappers
          - system:nodes
          rolearn: arn:aws:iam::123:role/KarpenterInstanceNodeRole
          username: system:node:{{EC2PrivateDNSName}}
    

您可以使用 $ kubectl edit configmap aws-auth -n kube-system 命令进行更改。

所有这些准备工作现在意味着我们可以下载并自定义 Karpenter Helm 图表。我们将使用版本 0.27.3,这是写作时的最新版本。以下命令可用于设置 Karpenter 版本并根据前面创建的环境变量和 IAM 策略自定义 Helm 图表,并将其保存为 karpenter.yaml 清单:

$ export KARPENTER_VERSION=v0.27.3
$ helm template karpenter oci://public.ecr.aws/karpenter/karpenter --version ${KARPENTER_VERSION} --namespace karpenter     --set settings.aws.defaultInstanceProfile=KarpenterInstanceNodeRole  --set settings.aws.clusterName=${CLUSTER_NAME}     --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/KarpenterControllerRole-${CLUSTER_NAME}"     --set controller.resources.requests.cpu=1     --set controller.resources.requests.memory=1Gi     --set controller.resources.limits.cpu=1     --set controller.resources.limits.memory=1Gi > karpenter.yaml

我们现在需要向 karpenter.yaml 文件中添加一些额外的配置。

注意

行号对您来说可能不同。

修改亲和性规则(第 482 行)

这将在现有的节点组上部署 Karpenter:

- matchExpressions:
              - key: eks.amazonaws.com/nodegroup
                operator: In
                values:
                - ipv4mng

然后,我们使用以下命令创建命名空间并添加 Karpenter 自定义资源:

$ kubectl create namespace karpenter
namespace/karpenter created
$ kubectl create -f https://raw.githubusercontent.com/aws/karpenter/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.sh_provisioners.yaml
customresourcedefinition.apiextensions.k8s.io/provisioners.karpenter.sh created
$ kubectl create -f https://raw.githubusercontent.com/aws/karpenter/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.k8s.aws_awsnodetemplates.yaml
customresourcedefinition.apiextensions.k8s.io/awsnodetemplates.karpenter.k8s.aws created

最后,我们可以使用以下命令部署 Helm 图表并验证部署:

$ kubectl apply -f karpenter.yaml
poddisruptionbudget.policy/karpenter created
serviceaccount/karpenter created
secret/karpenter-cert created
configmap/config-logging created
……
validatingwebhookconfiguration.admissionregistration.k8s.io/validation.webhook.karpenter.k8s.aws created
$ kubectl get all  -n karpenter
NAME        READY   STATUS    RESTARTS   AGE
pod/karpenter-fcd8f5df6-l2h6k   1/1     Running   0   43s
pod/karpenter-fcd8f5df6-r2vzw   1/1     Running       43s
NAME   TYPE   CLUSTER-IP  ..
service/karpenter   ClusterIP   10.100.76.202   ..
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/karpenter   2/2     2         2           43s
NAME          DESIRED   CURRENT   READY   AGE
replicaset.apps/karpenter-fcd8f5df6   2  2    2       43s

注意

如果你从头开始跟随本章,你将会部署了 CA 和 Karpenter 控制器。你可以通过以下命令将 CA 禁用,缩放至零:kubectl scale deploy/cluster-autoscaler -n kube-system --replicas=0

现在我们已经安装了 Karpenter,接下来让我们测试它是否工作正常。

测试 Karpenter 自动扩缩

我们需要做的第一件事是创建一个Provisioner及其关联的AWSNodeTemplateProvisioner创建 Karpenter 用于创建节点的规则,并定义 Pod 选择规则。至少需要一个 Provisioner 才能使 Karpenter 工作。在下一个示例中,我们将允许 Karpenter 处理以下内容:

  • 创建按需 EC2 实例

  • 选择实例类型为c5.largem5.largem5.xlarge

  • 给任何新节点添加type=karpenter标签

  • 一旦 Pod 空置 30 秒,节点将被取消配置

一旦 CPU 和内存限制达到,Karpenter 也将被限制创建额外的节点。

AWSNodeTemplate 用于描述 AWS 特定配置的元素,如子网和安全组。你可以手动配置这些细节,但我们将使用前面章节中创建的发现标签。

以下示例清单可以使用$ kubectl create -f provisioner.yaml命令进行部署:

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  labels:
    type: karpenter
  requirements:
    - key: karpenter.sh/capacity-type
      operator: In
      values: ["on-demand"]
    - key: "node.kubernetes.io/instance-type"
      operator: In
      values: ["c5.large", "m5.large", "m5.xlarge"]
  limits:
    resources:
      cpu: 1000
      memory: 1000Gi
  providerRef:
    name: default
  ttlSecondsAfterEmpty: 30
---
apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
  name: default
spec:
  subnetSelector:
    karpenter.sh/discovery: myipv4cluster
  securityGroupSelector:
    karpenter.sh/discovery: myipv4cluster

一旦我们成功部署了type=karpenter标签:

apiVersion: v1
kind: Namespace
metadata:
  name: other
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inflate
  namespace: other
spec:
  replicas: 0
  selector:
    matchLabels:
      app: inflate
  template:
    metadata:
      labels:
        app: inflate
    spec:
      nodeSelector:
        type: karpenter
      terminationGracePeriodSeconds: 0
      containers:
        - name: inflate
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
          resources:
            requests:
              memory: 1Gi

使用以下命令,我们可以部署之前的清单,验证没有副本存在,并检查是否没有节点具有type=karpenter标签:

$ kubectl create -f deployment.yaml
namespace/other created
deployment.apps/inflate created
$ kubectl get all -n other
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/inflate   0/0     0       0       11s
NAME        DESIRED   CURRENT   READY   AGE
replicaset.apps/inflate-11   0         0         0       11s
$ kubectl get node -l type=karpenter
No resources found

现在如果我们扩展部署,我们会看到 Pods 处于Pending状态,因为没有 EKS 节点满足Running状态。以下命令展示了这个流程:

$ kubectl scale -n other deployment/inflate --replicas 5
deployment.apps/inflate scaled
$ kubectl get po -n other
NAME                       READY   STATUS    RESTARTS   AGE
inflate-6cc55bfc74-4rfts   0/1     Pending   0          10s
inflate-6cc55bfc74-9kl5n   0/1     Pending   0          10s
inflate-6cc55bfc74-nspbf   0/1     Pending   0          10s
inflate-6cc55bfc74-rq4cn   0/1     Pending   0          10s
inflate-6cc55bfc74-swtmv   0/1     Pending   0          10s
$ kubectl get node -l type=karpenter
NAME     STATUS   ROLES    AGE   VERSION
ip-192-168-55-119.eu-central-1.compute.internal   Ready    <none>   99s   v1.22.17-eks-a59e1f0
$ kubectl get po -n other
NAME                       READY   STATUS    RESTARTS   AGE
inflate-6cc55bfc74-4rfts   1/1     Running   0          2m36s
inflate-6cc55bfc74-9kl5n   1/1     Running   0          2m36s
inflate-6cc55bfc74-nspbf   1/1     Running   0          2m36s
inflate-6cc55bfc74-rq4cn   1/1     Running   0          2m36s
inflate-6cc55bfc74-swtmv   1/1     Running   0          2m36s

如果我们现在删除该部署,我们将看到节点被取消配置:

$ kubectl delete -f deployment.yaml
namespace "other" deleted
deployment.apps "inflate" deleted
$ kubectl get node -l type=karpenter
NAME           STATUS   ROLES    AGE     VERSION
ip-192-168-55-119.eu-central-1.compute.internal   Ready    <none>   5m57s   v1.22.17-eks-a59e1f0
$ kubectl get node -l type=karpenter
No resources found

注意

与 CA 相比,扩展过程要快得多,这是 Karpenter 的一个关键优势。

我们已经专注于使用 CA 或 Karpenter 扩展底层 EKS 计算节点。这两个控制器都会查找由于资源问题而处于Pending状态的 Pods,直到现在我们一直在手动创建和扩展 Pods。现在我们将探讨如何使用 HPA 自动扩展 Pods。

使用水平 Pod 自动扩缩器扩展应用程序

HPA 是 Kubernetes 的一个组件,允许你根据度量标准(而非手动扩缩命令)来扩展 Pods(通过Deployment/ReplicaSet)。这些度量由 K8s 度量服务器收集,因此你需要在集群中部署该服务器。以下图示展示了大致流程。

图 18.7 – 高级 HPA 流程

图 18.7 – 高级 HPA 流程

在前面的图示中,我们可以看到以下内容:

  1. HPA 从度量服务器读取指标。

  2. 有一个控制循环每 15 秒触发 HPA 读取指标。

  3. HPA 会将这些指标与自动扩缩容配置的期望状态进行比较,并在需要时扩展部署。

现在我们已经了解了 HPA 背后的概念,让我们来配置并测试它。

在你的 EKS 集群中安装 HPA

如我们所讨论的,HPA 是 K8s 的一项功能,因此无需安装;然而,它依赖于 K8s Metrics Server。要检查是否安装了 Metrics Server 并提供数据,可以使用以下命令:

$ kubectl -n kube-system get deployment/metrics-server
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
metrics-server   1/1     1            1           34h
$ kubectl top pod -n kube-system
NAME                              CPU(cores)   MEMORY(bytes)
aws-node-2tx2g                    3m           38Mi
aws-node-9rhjs                    3m           38Mi
coredns-7b6fd76bcb-6h9b6          1m           12Mi
coredns-7b6fd76bcb-vsvv8          1m           12Mi
kube-proxy-rn96m                  1m           11Mi
kube-proxy-tf8d9                  1m           10Mi
metrics-server-68c56b9d8c-24mng   3m           17Mi

如果你没有安装 Metrics Server,可以使用以下命令安装最新版本:

$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

现在我们已经安装了 HPA 的先决条件,让我们测试一下它是否正常工作。

测试 HPA 自动扩缩容

为了进行测试,我们将使用 K8s 示例部署一个基于 php-apache 容器镜像的标准 Web 服务器。然后我们添加 HPA 自动扩缩容配置,使用负载生成器生成负载,推动指标增高,触发 HPA 扩展部署。使用的 K8s 清单文件如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: registry.k8s.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
  name: php-apache
  labels:
    run: php-apache
spec:
  ports:
  - port: 80
  selector:
    run: php-apache

我们现在可以部署这个清单并添加自动扩缩容配置,以便使用以下命令保持 CPU 利用率在 1-10 个 pod 上大约为 50%:

$ kubectl create -f hpa-deployment.yaml
deployment.apps/php-apache created
service/php-apache created
$ kubectl get all
NAME      READY   STATUS    RESTARTS   AGE
pod/php-apache-11-22   1/1     Running   0          57s
NAME     TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/php-apache   ClusterIP   10.1.2.1 <none>  80/TCP   57s
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/php-apache   1/1     1            1    58s
NAME           DESIRED   CURRENT   READY   AGE
replicaset.apps/php-apache-1122   1         1         1   58s
$ kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
horizontalpodautoscaler.autoscaling/php-apache autoscaled
$ kubectl get hpa
NAME    REFERENCE  TARGETS   MINPODS   MAXPODS REPLICAS   AGE
php-apache Deployment/php-apache   0%/50% 1  10   1       19s

现在我们已经添加了自动扩缩容配置,我们可以生成一些负载。随着负载下 pod 的 CPU 利用率上升,HPA 将修改部署,进行扩缩容以保持 CPU 利用率在 50%。

注意

你需要多个终端会话来进行这个练习。

在第一个终端会话中,运行以下命令以产生额外的负载:

$ kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"
If you don't see a command prompt, try pressing enter.
OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!.

在第二个终端中,我们可以查看 HPA 的统计数据,会看到随着 HPA 扩展部署以应对增加的负载,副本数会逐渐增加。

你可以看到,TARGETS(以下输出中的第三列)随着负载的增加最初高于 50% 的目标,然后当 HPA 增加更多副本时,值逐渐下降,直到在 5 个副本时低于 50% 的目标。这意味着 HPA 不应该再添加更多副本:

$ kubectl get hpa php-apache --watch
NAME  REFERENCE   TARGETS  MINPODS   MAXPODS   REPLICAS   AGE
php-apache Deployment/php-apache   119%/50%   1   10  1   10m
php-apache Deployment/php-apache   119%/50%   1   10  3   10m
php-apache Deployment/php-apache   188%/50%   1   10  3   10m
php-apache Deployment/php-apache   88%/50%    1   10  4   11m
php-apache Deployment/php-apache   77%/50%    1   10  4   11m
php-apache Deployment/php-apache   48%/50%    1   10  5   11m

如果现在终止在第一个会话中运行的容器,你将看到 HPA 会将部署缩回。接下来显示的 kubectl get hpa php-apache --watch 命令的输出,展示了当前负载值降到 0,并且 HPA 将副本数缩减到 1:

NAME  REFERENCE  TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
php-apache Deployment/php-apache 119%/50%   1  10  1      10m
php-apache Deployment/php-apache 119%/50%   1  10  3      10m
………………………………………
php-apache Deployment/php-apache 0%/50%  1    10  5     16m
php-apache Deployment/php-apache 0%/50%  1    10  1     16m

HPA 可以通过 metrics.k8s.io K8s API 端点查询核心指标,并且可以通过 external.metrics.k8s.iocustom.metrics.k8s.io API 端点查询自定义指标。对于更复杂的应用程序,你需要监控的不仅仅是CPU内存,所以让我们来看看如何使用自定义指标来扩展我们的应用程序。

使用自定义指标进行自动扩缩容

为了能够使用自定义指标,必须满足以下条件:

  1. 你的应用程序需要被仪表化以产生指标。

  2. 这些指标需要通过 custom.metrics.k8s.io 端点暴露。

应用程序开发者或开发团队负责第 1 点,我们将安装并使用 Prometheus 和 Prometheus 适配器来满足第 2 点。下图展示了该解决方案的高级流程。

图 18.8 – HPA 自定义指标的高级流程

图 18.8 – HPA 自定义指标的高级流程

让我们简要地看看流程。

  1. 安装在你的集群中的 Prometheus 服务器将从你的 Pod 中“抓取”自定义指标。

  2. HPA 将执行以下操作:

    1. 从 Prometheus 适配器上托管的 custom.metrics.k8s.io 自定义端点读取指标。

    2. Prometheus 适配器将从 Prometheus 服务器拉取数据。

  3. HPA 会根据自动扩缩配置的期望状态评估这些指标。该评估将参考自定义指标,例如每秒的 HTTP 请求平均数量,如果需要,HPA 将扩展部署。

现在我们已经了解了 HPA 自定义指标背后的概念,接下来让我们安装先决条件并进行测试。

在你的 EKS 集群中安装 Prometheus 组件

我们将首先使用 Helm 在集群中安装一个本地 Prometheus 服务器。接下来代码中展示的第一组命令用于获取最新的 Prometheus 服务器图表:

$ kubectl create namespace prometheus
namespace/prometheus created
$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
"prometheus-community" has been added to your repositories
$ helm repo update
Hang tight while we grab the latest from your chart repositories…
….....
…Successfully got an update from the "prometheus-community" chart repository
…Successfully got an update from the "stable" chart repository
Update Complete. Happy Helming

注意

如果你愿意,也可以使用 AWS 托管的 Prometheus 服务。

我们现在可以安装图表并验证所有 Pod 是否启动,使用以下命令:

$ helm upgrade -i prometheus prometheus-community/prometheus     --namespace prometheus     --set alertmanager.persistentVolume.storageClass="gp2",server.persistentVolume.storageClass="gp2"
Release "prometheus" does not exist. Installing it now.
NAME: prometheus
LAST DEPLOYED: Sun May  7 13:16:28 2023
NAMESPACE: prometheus
STATUS: deployed
REVISION: 1
TEST SUITE: None
……….
For more information on running Prometheus, visit:
https://prometheus.io/
$ kubectl get po -n prometheus
NAME          READY   STATUS    RESTARTS   AGE
prometheus-alertmanager-0  1/1     Running   0       19m
prometheus-kube-state-metrics-12  1/1  Running   0 19m
prometheus-prometheus-node-exporter-12 1/1 Running   0    19m
prometheus-prometheus-node-exporter-12  1/1 Running   0   19m
prometheus-prometheus-pushgateway-13   1/1  Running  0    19m
prometheus-server-677fbf6f-14       2/2     Running   0   19m

为了验证 Prometheus 是否正常工作,我们可以使用接下来的端口转发命令,然后使用本地浏览器访问 http://localhost:8080

$ kubectl --namespace=prometheus port-forward deploy/prometheus-server 8080:9090
Forwarding from 127.0.0.1:8080 -> 9090
Forwarding from [::1]:8080 -> 9090

然后我们可以使用指标浏览器(下图中突出显示的图标)以表格或图形格式获取数据。下图示例展示了 container_memory_usage_bytes 指标。

图 18.9 – Prometheus 服务器显示的指标数据

图 18.9 – Prometheus 服务器显示的指标数据

现在我们已经安装并使 Prometheus 服务器实例正常工作,我们可以安装 podinfo 应用程序。这是一个小型微服务,通常用于测试,并暴露了多个指标和健康 API。

注意

podinfo 至少需要 Kubernetes 1.23,因此请确保你的集群运行的是正确版本。我们还将安装一个 HPA 配置,稍后会替换它。

使用以下命令,我们可以部署 Pod、其服务和 HPA 配置:

$ kubectl version help
…
Server Version: version.Info{Major:"1", Minor:"23+", GitVersion:"v1.23.17-eks-0a21954", GitCommit:"cd5c12c51b0899612375453f7a7c2e7b6563f5e9", GitTreeState:"clean", BuildDate:"2023-04-15T00:32:27Z", GoVersion:"go1.19.6", Compiler:"gc", Platform:"linux/amd64"}
$ kubectl create ns podinfo
namespace/podinfo created
$ kubectl apply -k github.com/stefanprodan/podinfo/kustomize -n podinfo
service/podinfo created
deployment.apps/podinfo created
horizontalpodautoscaler.autoscaling/podinfo created
$ kubectl get hpa -n podinfo
NAME  REFERENCE  TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
podinfo   Deployment/podinfo   2%/99%    2   4     2  13m
$ kubectl get po -n podinfo
NAME                       READY   STATUS    RESTARTS   AGE
podinfo-78989955bc-12   1/1     Running   0          35m
podinfo-78989955bc-13   1/1     Running   0          13m

如果我们查看 podinfo GitHub 仓库中的 deployment.yaml 文件,我们可以看到以下两个注解:

        prometheus.io/scrape: "true"
        prometheus.io/port: "9797"

这意味着 Prometheus 可以自动抓取 9797 端口上的 /metrics 端点,因此如果我们在 Prometheus 服务器中查看(使用端口转发),我们可以看到从 podinfo Pod 中收集的一个指标是 http_requests_total,我们将使用它作为自定义指标。如下图所示。

图 18.10 – 在 Prometheus 中查看 podinfo 自定义指标数据

图 18.10 – 在 Prometheus 中查看 podinfo 自定义指标数据

由于我们现在有一个正在运行的 Prometheus 服务器,正在从我们的应用程序(podinfo 部署)收集自定义指标,接下来我们需要通过安装 Prometheus 适配器将这些指标连接到 custom.metrics.k8s.io 端点,使用以下命令:

适配器将安装在 Prometheus 命名空间中,并指向本地 Prometheus 服务器和端口。适配器将配置默认的度量标准集进行启动,我们可以通过以下命令查询custom.metrics.k8s.io端点来查看这些度量数据:

$ helm install prometheus-adapter prometheus-community/prometheus-adapter  --set prometheus.url="http://prometheus-server.prometheus.svc",prometheus.port="80" --set image.tag="v0.10.0" --set rbac.create="true" --namespace prometheusNAME: prometheus-adapter
…..
$ kubectl get po -n prometheus
NAME        READY   STATUS    RESTARTS   AGE
prometheus-adapter-123-chsmv  1/1     Running   0    92s
….
$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | jq .
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "custom.metrics.k8s.io/v1beta1",
  "resources": [
    {
      "name": "nodes/node_vmstat_pgpgin",
…..

配置我们度量标准的最简单方法是替换默认的 prometheus-adapter ConfigMap,使用以下显示的配置,该配置仅包含来自 podinfo 应用程序通过 /metrics 端点导出的 http_requests_total 度量的规则:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-adapter
  namespace: prometheus
data:
  config.yaml: |
    rules:
    - seriesQuery: 'http_requests_total'
      resources:
        overrides:
          namespace:
            resource: "namespace"
          pod:
            resource: "pod"
      name:
        matches: "^(.*)_total"
        as: "${1}_per_second"
      metricsQuery: 'rate(http_requests_total{namespace="podinfo",app="podinfo"}[2m])'

现在我们可以使用之前显示的配置替换 ConfigMap,重新启动部署以重新读取新的 ConfigMap,并使用以下命令通过自定义端点查询度量数据:

$ kubectl replace cm prometheus-adapter -n prometheus -f cm-adapter.yaml
configmap/prometheus-adapter replaced
$ kubectl rollout restart deployment prometheus-adapter -n prometheus
deployment.apps/prometheus-adapter restarted
$ kubectl get po -n prometheus
NAME     READY   STATUS    RESTARTS   AGE
prometheus-adapter-123-h7ztg  1/1     Running   0    60s
……
$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | jq .
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "custom.metrics.k8s.io/v1beta1",
  "resources": [
    {
      "name": "pods/http_requests_per_second",
      "singularName": "",
      "namespaced": true,
      "kind": "MetricValueList",
      "verbs": [
        "get"
      ]
    },
    {
      "name": "namespaces/http_requests_per_second",
      "singularName": "",
      "namespaced": false,
      "kind": "MetricValueList",
      "verbs": [
        "get"
      ]}]}
$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/podinfo/pods/*/http_requests_per_second" | jq .
{
  "kind": "MetricValueList",
  "apiVersion": "custom.metrics.k8s.io/v1beta1",
  "metadata": {},
  "items": [
    {
      "describedObject": {
        "kind": "Pod",
        "namespace": "podinfo",
        "name": "podinfo-78989955bc-89n8m",
        "apiVersion": "/v1"
      },
      "metricName": "http_requests_per_second",
      "timestamp": "2023-05-08T22:42:11Z",
      "value": "200m",
      "selector": null
    },
    {
      "describedObject": {
        "kind": "Pod",
        "namespace": "podinfo",
        "name": "podinfo-78989955bc-rnr9c",
        "apiVersion": "/v1"
      },
      "metricName": "http_requests_per_second",
      "timestamp": "2023-05-08T22:42:11Z",
      "value": "200m",
      "selector": null
    }]}

现在我们通过自定义指标端点暴露了 podinfo 度量数据,我们可以将 HPA 配置替换为使用自定义指标的配置,而不是最初部署时使用的标准 CPU 指标。为此,我们使用以下配置:

---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: podinfo
  namespace: podinfo
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: podinfo
  minReplicas: 1
  maxReplicas: 10
  metrics:
    - type: Pods
      pods:
        metricName: http_requests_per_second
        targetAverageValue: 10

我们可以使用以下命令来替换 HPA 配置并验证其有效性:

$ kubectl replace -f hpa-requests.yaml
Warning: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler
horizontalpodautoscaler.autoscaling/podinfo replaced
$ kubectl describe hpa podinfo -n podinfo
Warning: autoscaling/v2beta2 HorizontalPodAutoscaler is deprecated in v1.23+, unavailable in v1.26+; use autoscaling/v2 HorizontalPodAutoscaler
Name:                                  podinfo
Namespace:                             podinfo
Labels:                                <none>
Annotations:                           <none>
CreationTimestamp:                     Sun, 07 May 2023 15:30:19 +0000
Reference:                             Deployment/podinfo
Metrics:                               ( current / target )
  "http_requests_per_second" on pods:  200m / 10
Min replicas:                          1
Max replicas:                          10
Deployment pods:                       1 current / 1 desired
…….

现在我们已安装了 HPA 的前提条件,接下来让我们测试它是否正常工作。

使用自定义指标测试 HPA 自动扩展

为了进行测试,我们将使用一个简单的镜像,该镜像已安装 curl,并反复调用 podinfo API,增加请求次数。

注意

本次练习需要多个终端会话。

在第一个终端会话中,运行以下命令以生成负载:

$ kubectl run -it load-test --rm --image=nginx -n prometheus – bash
root@load-test:/# curl http://podinfo.podinfo.svc.cluster.local:9898
{
"hostname": "podinfo-78989955bc-zfwdj",
  "version": "6.3.6",
  "revision": "073f1ec5aff930bd3411d33534e91cbe23302324",
  "color": "#34577c",
  "logo": "https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif",
  "message": "greetings from podinfo v6.3.6",
  "goos": "linux",
  "goarch": "amd64",
  "runtime": "go1.20.4",
  "num_goroutine": "9",
  "num_cpu": "2"
}
root@load-test:/# while sleep 0.01; do curl http://podinfo.podinfo.svc.cluster.local:9898; done
……..

在第二个终端会话中,我们可以查看 HPA 的统计信息,并看到副本数随着 HPA 扩展部署以应对增加的负载逐渐增加。

以下输出中的第三列TARGETS最初较低,但随着更多请求的响应,它会逐渐增加。一旦超过阈值,就会添加更多副本:

$ kubectl get hpa -n podinfo --watch
NAME   REFERENCE TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
podinfo Deployment/podinfo   200m/10       1 10  1   31h
podinfo   Deployment/podinfo   216m/10     1 10  1   31h
podinfo   Deployment/podinfo   19200m/10   1 10  1   31h
podinfo   Deployment/podinfo   19200m/10   1 10  2   31h
podinfo   Deployment/podinfo   20483m/10   1 10  2   31h
podinfo   Deployment/podinfo   14868m/10   1 10  2   31h
podinfo   Deployment/podinfo   15744m/10   1 10  3   31h

注意

输出中显示的 m 字符表示毫单位,意味着每秒 0.1 个请求。

如果你退出第一个终端会话中的循环和容器,HPA 将逐渐缩减回单一副本。虽然这种解决方案有效,但并不适合大型生产环境。在本章的最后部分,我们将探讨如何使用Kubernetes 事件驱动自动扩展KEDA)进行 Pod 自动扩展,它支持大型环境,并且可以使用来自外部源的事件或指标。

使用 Kubernetes 事件驱动自动扩展(Event-Driven Autoscaling)

KEDA 是一个开源框架,允许你根据指标或事件扩展 K8s 工作负载。我们通过部署 KEDA 操作器来实现这一点,KEDA 操作器管理所有必需的组件,广义上包括以下内容:

  • 一个代理,负责根据事件对部署进行上下扩展。

  • 一个指标服务器,暴露来自应用程序或外部源的指标。

  • 一个ScaledObject自定义资源,维护外部源或指标与 K8s 部署之间的映射关系,以及扩展规则。这实际上创建了一个相应的 HPA Kind

  • 用于触发 KEDA 操作的内部和外部事件源。

以下图示展示了主要的 KEDA 组件。

图 18.11 – 主要的 KEDA 组件

图 18.11 – 主要的 KEDA 组件

现在我们已经了解了 KEDA 自定义指标背后的概念,接下来让我们安装并测试它。

在 EKS 集群中安装 KEDA 组件

我们将使用在前一个练习中创建的现有 Prometheus 和 podinfo 部署,但我建议首先使用以下命令移除 Prometheus 适配器,以避免调度冲突:

$ helm delete prometheus-adapter  --namespace prometheus
release "prometheus-adapter" uninstalled

现在,我们可以使用以下命令部署 KEDA 操作器:

helm repo add kedacore https://kedacore.github.io/charts
helm repo update
kubectl create namespace keda
helm install keda kedacore/keda --namespace keda –version 2.9.4
$ kubectl get po -n keda
NAME                READY   STATUS    RESTARTS   AGE
keda-operator-123-5cv    1/1     Running   0          7m49s
keda-operator-metrics-apiserver-123   1/1     Running     7m49s

注意

由于我们使用的是 K8s 版本 1.23,我们需要安装 2.9 版本。撰写本文时,2.9.4 是最新版本。你可以使用helm search repo kedacore -l命令获取最新的 Chart 版本。

我们现在需要部署ScaledObject,告诉 KEDA 监控什么(指标名称)、外部源是什么(Prometheus)、扩展什么(podinfo 部署),以及阈值是多少(10)。请注意,在这里显示的示例配置中,我们将minReplicaCount设置为2,但默认值是0

---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: prometheus-scaledobject
  namespace: podinfo
spec:
  scaleTargetRef:
    name: podinfo
  minReplicaCount:  2
  maxReplicaCount:  10
  pollingInterval: 10
  cooldownPeriod:  30
  fallback:
    failureThreshold: 3
    replicas: 2
  triggers:
  - type: prometheus
    metadata:
      serverAddress: http://prometheus-server.prometheus.svc.cluster.local:80
      metricName: http_requests_total
      threshold: '10'
      query: sum(rate(http_requests_total{namespace="podinfo",app="podinfo"}[2m]))

我们可以使用以下命令来部署并验证此配置。查看 HPA 配置,我们可以看到两种配置——一个由 KEDA 管理的keda-hpa-prometheus-scaledobject,另一个是使用原始 HPA 调度部署的:

$ kubectl create -f scaledobject.yaml
scaledobject.keda.sh/prometheus-scaledobject created
$ kubectl get ScaledObject -n podinfo
AME                      SCALETARGETKIND      SCALETARGETNAME    MIN   MAX   TRIGGERS     AUTHENTICATION   READY   ACTIVE   FALLBACK   AGE
prometheus-scaledobject   apps/v1.Deployment   podinfo           2     10    prometheus                    True    True     True       4m25s                    False   False    False
$ kubectl get hpa -n podinfo
NAME    REFERENCE  TARGETS  MINPODS   MAXPODS   REPLICAS   AGE
keda-hpa-prometheus-scaledobject   Deployment/podinfo   200m/10 (avg)   2         10        2          4m42s
podinfo                            Deployment/podinfo   2%/99%  2         4         2          84m

现在我们已经安装了 KEDA 并部署了ACTIVE扩展配置,接下来让我们测试它是否正常工作。

测试 KEDA 自动扩展

为了测试这个,我们将使用一个简单的镜像,并安装 curl 工具,然后反复调用 podinfo API,增加请求次数。

注意

你需要多个终端会话来进行此练习。

在第一个终端会话中,运行以下命令来生成负载:

$ kubectl run -it load-test --rm --image=nginx -n prometheus -- bash
root@load-test:/# while sleep 0.01; do curl http://podinfo.podinfo.svc.cluster.local:9898; done
……..

在第二个终端会话中,你可以查看 HPA 的统计数据,看到副本数量逐渐增加,随着 HPA 扩展部署以应对增加的负载。

以下输出中的第三列TARGETS最初较低,并随着响应请求数量的增加而增高。一旦超过阈值,就会添加更多副本。你会注意到副本数会波动,这是因为,与原生 HPA 不同,KEDA 会动态调整副本数量以满足不断增加的需求。

如果我们将 minReplicaCount 保持为 0,我们会看到更大的波动。

注意

这突出了使用 KEDA 时的一个关键考虑因素:如果你需要应对波动、不确定的需求,那么 KEDA 是理想的选择。

以下命令将展示 HPA 如何对 pods 进行扩展和收缩:

$ kubectl get hpa keda-hpa-prometheus-scaledobject -n podinfo --watch
NAME   REFERENCE  TARGETS  MINPODS   MAXPODS   REPLICAS   AGE
keda-hpa-prometheus-scaledobject   Deployment/podinfo   16742m/10 (avg)   2         10        2          16m
keda-hpa-prometheus-scaledobject   Deployment/podinfo   8371m/10 (avg)   2         10        4          16m
keda-hpa-prometheus-scaledobject   Deployment/podinfo   16742m/10 (avg)   2         10        2          16m
keda-hpa-prometheus-scaledobject   Deployment/podinfo   8371m/10 (avg)    2         10        4          16m
keda-hpa-prometheus-scaledobject   Deployment/podinfo   4961m/10 (avg)    2         10        3          17m

如果你退出第一个终端会话中的循环和容器,KEDA 会迅速缩放回 minReplicaCount 设置的值。

在本节中,我们探讨了扩展我们的集群及其工作负载的不同方式。现在我们将回顾本章的关键学习点。

总结

在本章中,我们探讨了不同的方式来扩展 EKS 计算节点(EC2),以提高弹性和/或性能。我们回顾了集群的不同扩展维度,然后使用标准的 K8s CA 设置了节点组/ASG 扩展。接着我们讨论了 CA 可能需要一些时间来操作,并且只限于 ASGs,如何使用 Karpenter 进行更快速的扩展,无需节点组,意味着你可以配置多种不同的实例类型。我们部署了 Karpenter,并展示了它如何使用不同的实例类型比 CA 更快速地扩展 EC2 基础的工作节点。

一旦我们回顾了如何扩展工作节点,我们就讨论了如何使用 HPA 在工作节点之间扩展 pods。我们首先了解了基本的 HPA 功能,它利用 K8s Metrics Server 来监控 pod 的 CPU 和内存统计数据,根据需要向部署中添加或移除 pods。接着我们考虑到复杂应用通常需要基于不同的特定指标进行扩展,并探讨了如何部署本地 Prometheus 服务器和 Prometheus 适配器,以获取自定义应用指标并通过 K8s 自定义指标端点暴露这些指标,从而根据这些自定义指标扩展我们的部署。

最后,我们回顾了如何使用 KEDA 利用自定义指标或外部数据源来应对波动的需求,并根据这些事件非常快速地扩展或收缩 pods。

在下一章中,我们将探讨如何在 EKS 上开发,涵盖 AWS 工具,如 Cloud9 和 CI/CD 工具,如 Argo CD。

进一步阅读

  • EKS 可观察性工具:

docs.aws.amazon.com/eks/latest/userguide/eks-observe.html

  • 了解 podinfo:

github.com/stefanprodan/podinfo

  • 使用带有 Prometheus 适配器的托管服务 Prometheus:

aws.amazon.com/blogs/mt/automated-scaling-of-applications-running-on-eks-using-custom-metric-collected-by-amazon-prometheus-using-prometheus-adapter/

  • 了解 KEDA:

keda.sh/docs/2.10/concepts/

  • 哪个 KEDA 版本支持哪个 K8s 版本?

keda.sh/docs/2.10/operate/cluster/

第十九章:在 EKS 上开发

在整本书中,我们已经探讨了如何构建 EKS 集群和部署工作负载。在本章中,我们将探讨一些方法,帮助开发人员或 DevOps 工程师通过自动化和 CI/CD 使这些活动更加高效。

在本章中,我们将讨论你可以用来在 AWS 上原生部署和测试集群及工作负载的工具和技术,或者使用第三方工具。我们将涵盖以下内容:

  • 不同的 IT 角色

  • 使用 Cloud9 作为你的集成开发环境

  • 使用 EKS 蓝图和 Terraform 构建集群

  • 使用 CodePipeline 和 CodeBuild 构建集群

  • 使用 Argo CD、Crossplane 和 GitOps 部署工作负载

技术要求

你应该熟悉 YAML、AWS IAM 和 EKS 架构。在开始本章之前,请确保以下几点:

  • 你可以访问到你的 EKS 集群 API 端点

  • AWS CLI、Docker 和kubectl二进制文件已安装在你的工作站,并且你拥有管理员权限

不同的 IT 角色

在我们探讨支持开发的技术之前,首先需要考虑谁将负责在你的组织或团队中部署 EKS 集群或应用/工作负载。下图展示了你可能在典型企业中找到的 IT 职能组,这通常被称为云操作模型,包括以下内容:

  • 构建应用的应用工程师

  • 操作和支持应用的应用运维

  • 构建中间件、网络、数据库等的平台工程师

  • 操作和支持基础设施及中间件的平台运维

图 19.1 – 云操作模型功能架构

图 19.1 – 云操作模型功能架构

许多组织现在采用 DevOps 模型,将应用工程应用运维结合在开发团队中,秉持“你构建,你运维”的理念。这也可以包括平台工程,但通常网络和数据库等传统 IT 运维团队仍然存在,他们必须与应用团队协作。

近年来,平台工程团队也开始出现,他们负责工程和支持开发者/DevOps 工程师使用的部分基础设施,例如 EKS、数据库、消息系统和 API。该团队的口号是“你编写代码和测试,我们负责所有其余的工作”。

具体使用哪种模型,以及各个团队的职责分配,实际上取决于你的组织。然而,在本节的其余部分,我们将使用DevOps 工程师来指代负责应用工程/运维的角色,平台工程师来指代负责 EKS 集群和支持基础设施(如数据库或网络)的角色。

让我们探讨如何与 AWS 环境进行交互,以构建、部署和测试平台和应用服务。

使用 Cloud9 作为集成开发环境

Cloud9 是一个简单的 集成开发环境 (IDE),运行在 EC2 上,类似于其他 IDE,例如微软的 Visual Studio Code、Eclipse 或 PyCharm。它可以被平台工程师或开发人员使用。虽然 Cloud9 没有那些 IDE 那样的可扩展性,但它也有一些优势,如下所示:

  • 它运行在您账户中的 EC2 上,这使得您可以与私有资源进行通信,例如私有 EKS 集群,而无需网络访问

  • 您可以使用 AWS 系统管理器会话管理器连接到您的实例,它只需要 IAM 权限和对 AWS 控制台的访问权限

  • 由于这是一个 EC2 实例,您可以为实例分配所需权限的角色,这些凭证会自动刷新且不会过期(这在配置集群时非常有用,因为集群配置可能需要一些时间)

  • 它提供了一个集成的 AWS 工具包,用于简化与 S3 和 Lambda 等资源的交互

  • 您可以在实例上运行 Docker 容器并预览 HTTP,如果您的容器或代码使用的是 808080818082 端口的 localhost 地址

  • 最近,它已与 Amazon CodeWhisperer 集成,CodeWhisperer 使用机器学习并可以为 Python 和 Java 等语言生成代码

在本书的开发过程中,我广泛使用了 Cloud9,因为它简单且安全,但您当然可以使用任何 IDE。在本节的其余部分,我们将讨论如何设置和使用 Cloud9 为 EKS 开发。

创建并配置您的 Cloud9 实例

我们将使用以下 Terraform 代码来创建一个 Cloud9 实例,允许 myuser_arn 本地定义的用户使用它,并将其连接到 subnet_id 中定义的子网。由于我们将连接类型定义为 CONNECT_SSM,因此只要该子网可以通过私有端点或 NAT 网关与 AWS SSM API 建立连接,它就可以是私有的:

data "aws_region" "current" {}
locals {
  myuser_arn = "arn:aws:sts::123:myuser"
}
resource "aws_cloud9_environment_ec2" "k8sdev" {
  name = "k8sdev"
  instance_type = "t3.medium"
  connection_type = "CONNECT_SSM"
  description = "cloud9 K8s development environment"
  subnet_id = "subnet-123"
  owner_arn = local.myuser_arn
}
data "aws_instance" "cloud9_instance" {
  filter {
    name = "tag:aws:cloud9:environment"
    values = [
    aws_cloud9_environment_ec2.k8sdev.id]
}}

注意

请注意,您可以根据自己的支付能力修改 instance_type,因为虽然 Cloud9 本身不收费,但托管它的 EC2 实例是收费的

一旦 Terraform 代码完成,您可以使用 AWS 控制台,浏览到 Cloud9 | 环境 标签页,并使用 打开 链接启动 EC2 实例并在浏览器中通过 SSM 会话启动 IDE。以下截图展示了这个过程。

图 19.2 – 启动 Cloud9 SSM 会话

图 19.2 – 启动 Cloud9 SSM 会话

默认情况下,Cloud9 将使用 AWS 管理的临时凭证,这些凭证具有有限的权限,可以通过以下链接查看:docs.aws.amazon.com/cloud9/latest/user-guide/security-iam.html#auth-and-access-control-temporary-managed-credentials-supported。这些凭证无法让你完全与 AWS 平台互动。我们将创建一个具有AdministratorAccess权限的角色,关闭 Cloud9 实例中的管理临时凭证,然后将这个新角色与托管 Cloud9 IDE 的 EC2 实例关联。

接下来展示的是角色描述,它与ec2.amazonaws.com服务有明确的信任关系。你可以按照以下链接中的过程来配置你的 Cloud9 实例:catalog.us-east-1.prod.workshops.aws/workshops/c15012ac-d05d-46b1-8a4a-205e7c9d93c9/en-US/15-aws-event/cloud9

图 19.3 – 一个示例 IAM 角色

图 19.3 – 一个示例 IAM 角色

注意

为简便起见,我们只添加了AdministratorAccess策略。理想情况下,你应该根据最小权限原则,定制 Cloud9 的权限,以满足所需的最低权限。

我们可以使用以下命令在 Cloud9 终端会话中验证角色是否已附加:

$ aws sts get-caller-identity
{"UserId": "343242342:i-12",
    "Account": "1122334455",
    "Arn": "arn:aws:sts::1234:assumed-role/cloud9-k8sdev/i-12" }

现在我们需要安装必要的工具;由于 Cloud9 自带了 AWS CLI、Python 和 Docker,但我们仍然需要安装kubectl等工具。你可以手动安装这些组件,但 AWS 提供了一个方便的脚本,作为 Cloud9 工作坊的一部分(如前述 URL 所示),所以我们将使用这个脚本来安装必要的工具,包括kubectl和 AWS 云开发工具包CDK)。相关命令如下:

$ wget https://jiwony-seoul-public.s3.ap-northeast-2.amazonaws.com/cloud9-prereq.sh
--2023-05-11 16:12:15--  https://jiwony-seoul-public.s3.ap-northeast-2.amazonaws.com/cloud9-prereq.sh
…….
$ sh cloud9-prereq.sh
Upgrading awscli
Requirement already up-to-date: awscli in
Complete!
---------------------------
You successfully installed all the required tools to your workspace
$  kubectl version --short
Flag --short has been deprecated, and will be removed in the future. The --short output will become the default.
Client Version: v1.27.1
Kustomize Version: v5.0.1
The connection to the server localhost:8080 was refused - did you specify the right host or port?
$ cdk version
2.78.0 (build 8e95c37)
****************************************************
*** Newer version of CDK is available [2.79.1]   ***
*** Upgrade recommended (npm install -g aws-cdk) ***
****************************************************

我们还将安装/升级terraform,因为我们稍后将在本节中使用它,安装命令如下:

$ sudo yum install -y yum-utils
$ sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
$ sudo yum -y install terraform

我们还可以添加在本书早期章节中使用的eksctl,可以通过以下命令进行安装:

$ ARCH=amd64
$ PLATFORM=$(uname -s)_$ARCH
$ curl -sLO "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$PLATFORM.tar.gz"
$ tar -xzf eksctl_$PLATFORM.tar.gz -C /tmp && rm eksctl_$PLATFORM.tar.gz
$ sudo mv /tmp/eksctl /usr/local/bin
$ eksctl version
0.140.0

最后,我们将设置默认区域,以便所有使用 SDK 的工具都能使用我们指定的区域:

$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]: eu-central-1
Default output format [None]:

现在,我们的 Cloud9 实例已经配置完毕。接下来,我们将使用它来通过 EKS 蓝图部署集群。

使用 EKS 蓝图和 Terraform 构建集群

在本书中,我们主要使用eksctl来构建我们的集群,并利用附加组件来简化标准组件的升级,例如 VPC CNI 插件或 kube-proxy。我们还部署了其他软件,如 Prometheus 和 KEDA(第十八章)。EKS 蓝图为你提供了一种构建具有预设方案的集群的方法,这些操作软件已经部署好。这简化了 DevOps 工程师平台的工作,他们可以使用蓝图反复构建适用于不同环境和/或团队的集群,几乎不需要任何额外努力。

EKS Blueprint 集群是使用 AWS CDK 构建的,AWS CDK 是一套库和构造,允许你使用标准编程语言(如 TypeScript 或 Python)创建和部署复杂的 CloudFormation 模板。最近,AWS 发布了 Terraform 版 EKS Blueprints,我们将在接下来的章节中使用它来创建一个可以供开发者用来部署应用程序的集群。

你可以按照阶段性的方法来开发集群配置。以下图表展示了推荐的方法。

图 19.4 – EKS 蓝图开发生命周期

图 19.4 – EKS 蓝图开发生命周期

在接下来的章节中,我们将逐步走过开发生命周期的每个阶段,下载、版本化并定制我们的蓝图代码。

定制和版本化 Terraform 的 EKS Blueprints

我们要做的第一件事是使用我们的 Cloud9 实例,在 CodeCommit 中创建一个符合 Git 的仓库,以存储我们的 Terraform 代码版本。以下命令可以用来创建仓库、克隆它并为我们的工作创建一个新分支:

$ aws codecommit create-repository --repository-name cluster-tf --repository-description "repository for TF Blueprint" --tags Team=devops --region eu-central-1
{
        "……..
        "cloneUrlHttp": "https://git-codecommit.eu-central-1.amazonaws.com/v1/repos/cluster-tf",
…}}
$ git clone https://git-codecommit.eu-central-1.amazonaws.com/v1/repos/cluster-tf
Cloning into 'cluster-tf'...
warning: You appear to have cloned an empty repository.
$ cd cluster-tf
(master) $ git checkout -b initial
Switched to a new branch 'initial'

在我们的 cluster-tf 目录中,我们将基于在 github.com/github/gitignore/blob/main/Terraform.gitignore 找到的模板创建一个新的 .gitignore 文件(你也可以创建自己的)。

设置基础变量和提供程序

要使用 Terraform 蓝图模块,我们需要配置关键的 Terraform 资源,例如使用的提供程序和数据源。让我们从提供程序开始,它们是 Terraform 的基础“引擎”,将 Terraform 资源转换为在 AWS 或 K8s 中实际部署的对象。以下配置保存在我们克隆仓库目录中的 providers.tf 文件中:

terraform {
  required_version = ">= 1.0.1"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.47"
    }
    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = ">= 2.10"
    }
    helm = {
      source  = "hashicorp/helm"
      version = ">= 2.4.1"
    }
    kubectl = {
      source  = "gavinbunney/kubectl"
      version = ">= 1.14"
    }
  }
}

我们还将创建一个 data.tf 文件,用于获取当前 AWS 凭证、区域和该区域的可用区:

data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
data "aws_availability_zones" "available" {
  state = "available"}

我们还将创建一个 local.tf 文件,保持基础配置,包括集群名称和版本。集群名称是从仓库路径中派生的,但对于生产使用,你可能需要使用 locals int 变量,并在构建时填充它们:

locals {
  name            = basename(path.cwd)
  region          = data.aws_region.current.name
  cluster_version = "1.24"
  vpc_cidr = "172.31.0.0/16"
  azs      = slice(data.aws_availability_zones.available.names, 0, 3)
  node_group_name = "mgmt-nodegroup"
  tags = {
    Blueprint  = local.name
    GithubRepo = "github.com/aws-ia/terraform-aws-eks-blueprints"
  }
}

我们现在可以运行以下命令,用提供程序初始化 Terraform 并将初始代码推送到我们的 CodeCommit 仓库:

(initial)$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching ">= 4.47.0"...
- Finding hashicorp/kubernetes versions matching ">= 2.10.0"...
…..
(initial)$ git add .
(initial)$ git commit -m "initial commit with providers and configuration"
….
(initial)$ git push --set-upstream origin initial
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
…..
branch 'initial' set up to track 'origin/initial'.
(initial)$

现在我们已经存储了提供程序和基础配置,我们可以使用它来创建一个 VPC 并将其标记为 EKS 使用。

创建 EKS VPC

你的集群需要一个现有的 VPC 用于 EKS 集群。我们将使用接下来的代码创建一个新的 VPC,但你也可以修改 创建 EKS 集群 部分中的代码,使用一个已有的 VPC,并跳过这一步:

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "3.16.0"
  name = local.name
  cidr = local.vpc_cidr
  azs  = local.azs
  public_subnets  = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)]
  private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 10)]
  enable_nat_gateway   = true
  create_igw           = true
  enable_dns_hostnames = true
  single_nat_gateway   = true
  manage_default_network_acl    = true
  default_network_acl_tags      = { Name = "${local.name}-default" }
  manage_default_route_table    = true
  default_route_table_tags      = { Name = "${local.name}-default" }
  manage_default_security_group = true
  default_security_group_tags   = { Name = "${local.name}-default" }
  public_subnet_tags = {
    "kubernetes.io/cluster/${local.name}" = "shared"
    "kubernetes.io/role/elb"              = "1"
  }
  private_subnet_tags = {
    "kubernetes.io/cluster/${local.name}" = "shared"
    "kubernetes.io/role/internal-elb"     = "1"
  }
    tags = local.tags
}

我们还将创建一个 outputs.tf 文件,用于存储新创建的 VPC 的 ID,该 ID 可以在我们使用以下代码创建 EKS 集群时使用:

output "vpc_id" {
  description = "The id of the new VPC"
  value       = module.vpc.vpc_id
}

我们现在可以验证代码是否正确,创建 VPC,并使用以下命令保存最终代码:

(initial)$ terraform init
Initializing the backend...
Downloading registry.terraform.io/terraform-aws-modules/vpc/aws 3.16.0 for vpc...
….
Terraform has been successfully initialized!
(initial)$ terraform plan
….
Plan: 23 to add, 0 to change, 0 to destroy.
Changes to Outputs:
  + vpc_id = (known after apply)
…
(initial)$ terraform apply --auto-approve
data.aws_region.current: Reading...
data.aws_caller_identity.current: Reading...
Apply complete! Resources: 23 added, 0 changed, 0 destroyed.
Outputs:
vpc_id = "vpc-0d5fb4e92b71eb9e6"
(initial)$ git add .
(initial)$ git commit -m "added vpc and deployed"
….
create mode 100644 vpc.tf
create mode 100644 outputs.tf
(initial) $ git push
Enumerating objects: 4, done.
…

注意

Terraform 将其状态存储在状态文件terraform.tfstate中。目前,它将被保存在本地仓库目录中,并被 Git 忽略(由于.gitignore文件)。我们将在本章稍后讨论管理此文件的策略。

现在我们有了一个新的 VPC,我们将使用 EKS 蓝图来配置并部署一个引用新 VPC 的 EKS 集群。

创建 EKS 集群

我们现在将使用 Blueprint 模块创建一个 EKS 集群;我们将使用 4.31.0 版本,这是目前写作时的最新版本。以下是main.tf文件中的示例配置。这将创建在我们之前创建的 VPC 中的集群,并且只包含标准的 K8s 服务:

provider "aws" {
  region = "us-east-1"
  alias  = "virginia"
}
provider "kubernetes" {
  host                   = module.eks_blueprints.eks_cluster_endpoint
  cluster_ca_certificate = base64decode(module.eks_blueprints.eks_cluster_certificate_authority_data)
  token                  = data.aws_eks_cluster_auth.this.token
}
provider "helm" {
  kubernetes {
    host                   = module.eks_blueprints.eks_cluster_endpoint
    cluster_ca_certificate = base64decode(module.eks_blueprints.eks_cluster_certificate_authority_data)
    token                  = data.aws_eks_cluster_auth.this.token
  }
}
provider "kubectl" {
  apply_retry_count      = 10
  host                   = module.eks_blueprints.eks_cluster_endpoint
  cluster_ca_certificate = base64decode(module.eks_blueprints.eks_cluster_certificate_authority_data)
  load_config_file       = false
  token                  = data.aws_eks_cluster_auth.this.token
}
module "eks_blueprints" {
  source = "github.com/aws-ia/terraform-aws-eks-blueprints?ref=v4.31.0"
  cluster_name    = local.name
  vpc_id             = module.vpc.vpc_id
  private_subnet_ids = module.vpc.private_subnets
  cluster_version = local.cluster_version
  managed_node_groups = {
    mg_5 = {
      node_group_name = local.node_group_name
      instance_types  = ["m5.large"]
      subnet_ids      = module.vpc.private_subnets
    }
  }
  tags = local.tags
}

注意

在之前显示的eks blueprints模块中,我们使用ref关键字来指示我们将调用哪个版本的蓝图模块;这可能会根据蓝图发布计划而有所变化。

我们需要为集群部署配置一些额外的数据源,包括一个位于其他区域的eu-east-1。以下是eks-data.tf文件中创建的示例配置:

data "aws_eks_cluster" "cluster" {
  name = module.eks_blueprints.eks_cluster_id
}
data "aws_eks_cluster_auth" "this" {
  name = module.eks_blueprints.eks_cluster_id
}
# To Authenticate with ECR Public in eu-east-1
data "aws_ecrpublic_authorization_token" "token" {
  provider = aws.virginia
}

我们还需要在eks-output.tf文件中配置一个额外的输出,如下所示,以便可以通过 Cloud9 实例手动与集群交互:

output "configure_kubectl" {
  description = "run the following command to update your kubeconfig"
  value       = module.eks_blueprints.configure_kubectl }

现在我们已经完成了所有配置,可以使用以下命令验证代码是否正确,创建 EKS 集群,并保存代码:

(initial)$ terraform init
Initializing the backend...
Initializing modules...
Downloading git::https://github.com/aws-ia/terraform-aws-eks-blueprints.git?ref=v4.31.0 for eks_blueprints...
- eks_blueprints in .terraform/modules/eks_blueprints
….
Terraform has been successfully initialized!
(initial)$ terraform plan
….
Plan: 32 to add, 0 to change, 0 to destroy.
Changes to Outputs:
  + vpc_id = (known after apply)
  + configure_kubectl = (known after apply)
(initial)$ terraform apply --auto-approve
data.aws_region.current: Reading...
data.aws_caller_identity.current: Reading...
…
Apply complete! Resources: 32 added, 0 changed, 0 destroyed.
…
Outputs:
configure_kubectl = "aws eks --region eu-central-1 update-kubeconfig --name cluster-tf"
vpc_id = "vpc-0d5fb4e92b71eb9e6"
(initial) $ aws eks --region eu-central-1 update-kubeconfig --name cluster-tf
Added new context arn:aws:eks:eu-central-1:123:cluster/cluster-tf to /home/ec2-user/.kube/config
(initial) $ kubectl get node
NAME   STATUS   ROLES    AGE     VERSION
ip-172-31-10-122.eu-central-1.compute.internal   Ready    <none>   2m52s   v1.24.11-eks-a59e1f0
ip-172-31-11-172.eu-central-1.compute.internal   Ready    <none>   2m48s   v1.24.11-eks-a59e1f0
ip-172-31-12-210.eu-central-1.compute.internal   Ready    <none>   2m49s   v1.24.11-eks-a59e1f0
(initial) $ git add .
(initial) $ git commit -m "deployed working cluster"
[initial dbf80aa] deployed working cluster
 5 files changed, 182 insertions(+)
 create mode 100644 README.md
 create mode 100644 eks-data.tf
 create mode 100644 eks-ouputs.tf
 create mode 100644 main.tf
 (initial) $ git push
Enumerating objects: 9, done.
….
To https://git-codecommit.eu-central-1.amazonaws.com/v1/repos/cluster-tf
   c5319f4..dbf80aa  initial -> initial

注意

请注意,部署集群可能需要最多 15 分钟的时间。

现在我们有了一个可用的集群,我们可以允许不同的用户、角色或团队访问。

将用户/团队添加到您的集群

目前,只有与您用于运行 terraform 的凭证相关的 role/identity 才能访问您的集群。因此,我们将添加一个新的管理员到集群,然后添加一个租户。

main.tf文件中,您可以添加一个map roles部分,这将为集群添加一个作为管理员的单一角色:

map_roles = [
    {
      rolearn  = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/Admin"
      username = "admin-role"
      groups   = ["system:masters"]
    }
  ]

注意

您需要将 role/admin 替换为帐户中的适当角色。请记住,如果在应用配置后 Terraform 使用的 IAM 凭证未包含在配置中,Terraform 可能会失去访问集群的权限,从而无法执行 K8s API 操作,例如修改aws-auth配置映射。

对于租户/用户,我们将创建一个新的locals文件locals-team.tf,但您可能希望使用变量。以下是针对两个团队(平台团队和应用程序团队)的示例:

locals {
platform_admins = ["arn:aws:iam::123:role/plat1"]
app_team_1 = ["arn:aws:iam::123:role/dev1"]
}

注意

您需要使用有效的用户帐户 ARN 或[data.aws_caller_identity.current.arn]

我们现在需要修改集群的 main.tf 文件,并添加以下代码片段 platform_teams,为一组平台团队用户提供集群管理员访问权限。这将创建一个具有集群访问权限的新 IAM 角色,并允许分配给平台团队的用户列表承担该角色并获得管理员访问权限:

  platform_teams = {
    admin = {
      users = local.platform_admins
    }}

main.tf 文件中,我们还可以添加一个租户 DevOps 或应用程序团队并设置限制,这也将创建一个命名空间:

application_teams = {
    alpha = {
      "labels" = {
        "appName"     = "alpha",
        "projectName" = "project-alpha",
        "environment" = "dev"
      }
      "quota" = {
        "pods"            = "15",
        "services"        = "10"
      }
      users         = [local.app_team_alpha]
    }}

我们现在可以执行常规的 Terraform planapply 命令来部署这些更改,授予对本地文件中列出的 ARN 的访问权限,并使用我们的标准 Git 命令将更改提交到我们的代码库。以下是主要命令的示例:

(initial) $ terraform plan
module.eks_blueprints.module.aws_eks.module.kms.data.aws_partition.current: Reading...
module.eks_blueprints.data.aws_region.current: Reading...
….
Plan: 9 to add, 3 to change, 0 to destroy..
(initial) $ terraform apply --auto-approve
odule.eks_blueprints.module.aws_eks.module.kms.data.aws_partition.current: Reading...
Note: Objects have changed outside of Terraform
…
Apply complete! Resources: 2 added, 1 changed, 0 destroyed.
Outputs:
configure_kubectl = "aws eks --region eu-central-1 update-kubeconfig --name cluster-tf"
vpc_id = "vpc-0d5fb4e92b71eb9e6"
(initial) $ kubectl get ns
NAME              STATUS   AGE
alpha             Active   10m
..
(initial) $ kubectl get ResourceQuota -n alpha
NAME     AGE   REQUEST                      LIMIT
quotas   10m   pods: 0/15, services: 0/10

如果你想查看每个团队需要配置的 kubectl 命令,可以将以下配置添加到 outputs.tf 文件:

output "platform_team_configure_kubectl" {
  description = "Configure kubectl for Platform Team"
  value       = try(module.eks_blueprints.teams[0].platform_teams_configure_kubectl["admin"], null) }
output "alpha_team_configure_kubectl" {
  description = "Configure kubectl for each Application Team "
  value       = try(module.eks_blueprints.teams[0].application_teams_configure_kubectl["alpha"], null) }

现在我们已经为平台工程团队和应用开发团队设置了正确的访问权限,我们可以部署多个插件。

将蓝图添加到您的集群

正如我们在前面的章节中看到的,部署像 AWS 负载均衡器控制器或 Karpenter 这样的工具可能需要相当多的工作。蓝图将 EKS 插件概念扩展到其他工具,并可以利用 EKS 插件或 ArgoCD 来部署这些软件。当前支持的插件可以在 aws-ia.github.io/terraform-aws-eks-blueprints/add-ons 找到。

我们将部署 ArgoCD,这是一个 GitOps 部署工具(在 使用 ArgoCD、Crossplane 和 GitOps 部署工作负载 章节中会更详细讨论),然后 ArgoCD 将部署(大部分)其他插件。

我们首先要做的是在我们的代码库中创建一个 locals-blueprints.tf 文件,文件内容如下所示。这将告诉 ArgoCD 在哪里查找不同的 Helm 图表来部署插件:

locals {
 addon_application = {
    path               = "chart"
    repo_url           = "https://github.com/aws-samples/eks-blueprints-add-ons.git"
    add_on_application = true }}

下一步是部署 argodCD 并告诉它要部署哪些插件。请注意,蓝图插件模块是有意见的,因此一些插件,如 AWS CSI 驱动程序,将直接作为 EKS 插件部署(并将作为插件显示),而其他插件将由 argoCD 处理。

我们将直接部署 argoCD 和 AWS EBS CSI 驱动程序,然后 Argo CD(argoCD)将部署以下内容:

  • AWS 负载均衡器控制器

  • Fluent Bit 用于日志记录

  • 用于标准度量的度量服务器

  • Karpenter 用于自动扩展

  • Crossplane(稍后讨论)用于基础设施即代码

以下代码片段将作为 blueprints.tf 使用:

module "kubernetes_addons" {
  source = "github.com/aws-ia/terraform-aws-eks-blueprints?ref=v4.31.0/modules/kubernetes-addons"
  eks_cluster_id     = module.eks_blueprints.eks_cluster_id
  enable_argocd         = true
  argocd_manage_add_ons = true
  argocd_applications = {
    addons    = local.addon_application}
  argocd_helm_config = {
    set = [{ name  = "server.service.type"
        value = "LoadBalancer" }]}
  enable_aws_load_balancer_controller  = true
  enable_amazon_eks_aws_ebs_csi_driver = true
  enable_aws_for_fluentbit             = true
  enable_metrics_server                = true
  enable_Crossplane                    = true
  enable_karpenter                     = true }

以下命令可以用于执行以下操作:

  • 部署 Terraform 更新

  • 验证 EKS 中的插件是否都已成功部署:

(initial) $ terraform init
Downloading git::https://github.com/aws-ia/terraform-aws-eks-blueprints.git?ref=v4.31.0 for kubernetes_addons...
….
(initial) $ terraform plan module.eks_blueprints.module.aws_eks.module.kms.data.aws_partition.current: Reading...
module.eks_blueprints.data.aws_region.current: Reading...
….
Plan: 29 to add, 0 to change, 0 to destroy.
(initial) $ terraform apply --auto-approve
odule.eks_blueprints.module.aws_eks.module.kms.data.aws_partition.current: Reading...
Note: Objects have changed outside of Terraform
…
Apply complete! Resources: 29 added, 0 changed, 0 destroyed.
Outputs:
configure_kubectl = "aws eks --region eu-central-1 update-kubeconfig --name cluster-tf"
vpc_id = "vpc-0d5fb4e92b71eb9e6"
(initial) $ aws eks list-addons --cluster-name cluster-tf
{"addons": [
        "aws-ebs-csi-driver"]}

我们现在可以获取 ArgoCD 的详细信息并访问它,以查看其他插件的详细信息,使用以下命令:

(initial) $ kubectl get deploy -n argocd
NAME                 READY   UP-TO-DATE   AVAILABLE   AGE
argo-cd-argocd-applicationset-controller   1/1  1 1 82m
argo-cd-argocd-dex-server                  1/1  1 1 82m
argo-cd-argocd-notifications-controller    1/1  1 1 82m
argo-cd-argocd-repo-server                 2/2  2 2 82m
argo-cd-argocd-server                      2/2  2 2 82m
argo-cd-redis-ha-haproxy                   3/3  3 3 82m
(initial) $ export ARGOCD_SERVER=`kubectl get svc argo-cd-argocd-server -n argocd -o json | jq --raw-output '.status.loadBalancer.ingress[0].hostname'`
(initial) $ echo https://$ARGOCD_SERVER
https://1234-453293485.eu-central-1.elb.amazonaws.com
(initial) $ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
Myinteretsingp355word

我们现在可以浏览到 ArgoCD URL,并使用前面显示的详细信息登录,查看其他插件的状态:

图 19.5 – ArgoCD 应用程序/附加组件状态

图 19.5 – ArgoCD 应用程序/附加组件状态

注意

请确保您已经将所有更改推送到 CodeCommit 仓库,并使用 terraform destroy 命令销毁了集群。销毁 VPC 和网络组件可能需要一些时间。

修改和升级遵循类似的模式;修改 Terraform 代码后,使用 Terraform 的 planapply 命令来升级或重新配置集群、节点组、访问权限和蓝图。

现在我们已经使用 Terraform 手动创建(和销毁)了资源,让我们来看一下如何使用 AWS CI/CD 工具来自动化集群的测试和部署。

使用 CodePipeline 和 CodeBuild 来构建集群

到目前为止,我们已经通过运行 Terraform 的 planapply 命令手动完成了部署。CodeBuild 是 AWS 的一项服务,作为 CI 构建服务器,同时也部署我们的 Terraform 配置。CodePipeline 自动化了端到端的发布管道,并基于对 CodeCommit 等代码库的提交,依次执行构建、测试和部署阶段。

我们需要做的第一件事是调整 Terraform 代码,以支持将状态存储在 S3 存储桶中。这是必要的,因为默认情况下,Terraform 会使用本地存储来存储其状态,而 CodeBuild 是一个临时环境,因此在构建之间该状态将会丢失。Terraform 依赖于状态文件来确定需要添加、修改或删除的内容。我们只需将下面显示的后端配置代码添加到之前创建的 providers.tf 文件中即可。我们不需要指定任何详细信息,因为这将在 Terraform init 阶段动态配置:

terraform {
…
  backend "s3" {}

注意

一旦修改了代码,请将更改提交到您的代码库。

接下来我们需要做的是将 buildspec.yml 文件添加到我们代码库的 root 目录中。此文件由 CodeBuild 用于运行构建/部署命令。buildspec 文件是一个特定格式的文件,包含多个阶段:

  • 安装 阶段,安装 Terraformjq 的最新版本。

  • 预构建 阶段,运行 terraform init 并配置它使用 S3 存储桶和特定区域的前缀,使用环境变量,还要运行 Terraform validate 命令作为基本测试。

  • 构建 阶段,可以根据环境变量中指定的操作,运行 Terraform 的 planapplydestroy 命令。

下面是 buildspec.yml 文件的一个示例:

version: 0.2
env:
  exported-variables:
    - BuildID
    - BuildTag
phases:
  install:
    commands:
      - yum update -y
      - yum install -y yum-utils
      - yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
      - yum -y install terraform jq
      - terraform version
  pre_build:
    commands:
      - echo creating S3 backend for bucket ${TFSTATE_BUCKET} region ${TFSTATE_REGION} prefix ${TFSTATE_KEY}
      - cd "$CODEBUILD_SRC_DIR"
      - terraform init -input=false -backend-config="bucket=${TFSTATE_BUCKET}" -backend-config="key=${TFSTATE_KEY}" -backend-config="region=${TFSTATE_REGION}"
      - terraform validate
  build:
    commands:
      - echo running command terraform ${TF_ACTION}
      - cd "$CODEBUILD_SRC_DIR"
      - terraform ${TF_ACTION} -input=false

一旦修改了代码,请将更改提交到您的代码库。

由于我们现在已经配置了 Terraform 后端,并且拥有一个 CodeBuild 用于构建/部署资源的 buildspec 文件,我们需要创建并配置构建项目。

设置 CodeBuild 项目

使用 AWS 控制台,导航到 AWS CodeBuild 服务并添加一个新的构建项目。填写 项目名称描述 字段,如下图所示:

图 19.6 – CodeBuild 项目配置

图 19.6 – CodeBuild 项目配置

CodeCommit 仓库和分支中,Terraform 代码和 buildspec 文件位于其中,如下图所示:

图 19.7 – CodeBuild 源代码配置

图 19.7 – CodeBuild 源代码配置

环境面板的第一部分,将构建环境定义为标准的 Linux 环境,如下图所示:

图 19.8 – CodeBuild 环境配置

图 19.8 – CodeBuild 环境配置

保持服务角色设置为创建新服务角色,并保持 Buildspec 面板原样(如下所示)。然后,点击屏幕底部的 创建构建项目(未显示):

图 19.9 – CodeBuild buildspec 配置

图 19.9 – CodeBuild buildspec 配置

您需要配置以下环境变量,这些变量由 buildspec 文件使用:

  • TFSTATE_BUCKET 指向现有的 S3 存储桶名称

  • TF_ACTION 将执行并 apply -auto-approve,但可以更改为 destroyplan 操作

  • cluster/cluster-tf/terraform.tfstate 的值

  • TFSTATE_REGION 指向正确的区域

图 19.10 – CodeBuild 环境变量

图 19.10 – CodeBuild 环境变量

配置好环境变量后,点击 创建 构建项目

注意

为项目创建的构建项目服务角色需要添加相关的 IAM 权限,以允许 Terraform 代码创建资源。

我们还应该将代码构建中明确使用的服务角色添加到 main.tf 代码中的 map_role 部分,如下所示:

{rolearn  = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/service-role/codebuild-terra-test-service-role"
      username = "admin-role"
      groups   = ["system:masters"] }

注意

service-role 名称替换为您的 CodeBuild 项目使用的名称,并提交您的更改到仓库。

现在,我们已经创建了一个指向我们的仓库和分支的项目,并且有一个特定的 buildspec.yml 文件,它提供了我们部署 Terraform 配置所需的命令。

图 19.11 – CodeBuild 启动构建下拉菜单

图 19.11 – CodeBuild 启动构建下拉菜单

一旦构建开始,您可以查看日志并查看任何错误,但它最终会完成,然后您可以查看构建历史。如果查看下图所示的示例,您可以看到部署 Terraform 仅花费了 30 分钟多一点,并且成功完成。

图 19.12 – CodeBuild 构建历史记录屏幕

图 19.12 – CodeBuild 构建历史记录屏幕

如果我们查看 S3 存储桶,我们可以看到我们在 TFSTATE_KEY 环境变量中定义的前缀和 terraform.tfstate 文件。

图 19.13 – S3 Terraform 状态文件

图 19.13 – S3 Terraform 状态文件

为了触发构建任务,我们需要点击 开始构建 按钮,或使用 CodeBuild API。接下来,我们将看看如何使用 CodePipeline 在代码更改时触发构建。

注意

在继续之前,你应该删除 Terraform 创建的资源,可以手动删除,也可以通过将构建任务的 TF_ACTION 改为 destroy -auto-approve 然后重新运行构建任务来删除。

设置 CodePipeline

当我们设置 CodePipeline 时,我们将配置两个阶段——一个 阶段,引用我们的 CodeCommit 仓库,其中包含 Terraform 和 buildspec 文件,和一个 构建 阶段,引用我们的 CodeBuild 项目。以下截图展示了一个示例:

图 19.14 – CodePipeline 阶段

图 19.14 – CodePipeline 阶段

我们将配置源阶段,使用我们的 CodeCommit 仓库和我们将用于代码的分支详细信息,并保留默认的更改检测和输出工件。这意味着当我们做出更改(提交)时,我们将触发一个 CloudWatch 事件,并在下一阶段使用它。以下是 CodeCommit 配置的示例:

图 19.15 – 一个 CodePipeline 源阶段配置片段

图 19.15 – 一个 CodePipeline 源阶段配置片段

然后我们需要配置我们的构建阶段,使其指向我们在正确区域中的现有 CodeBuild 项目。以下是 CodeBuild 配置的示例:

图 19.16 – 一个 CodePipeline 构建阶段配置片段

图 19.16 – 一个 CodePipeline 构建阶段配置片段

现在,当我们进行提交时,CodePipeline 会检测到它并触发 CodeBuild 运行我们之前创建的构建项目。我们可以看到下面的示例中,提交 ID 和消息被显示为触发器:

图 19.17 – 一个成功的管道运行

图 19.17 – 一个成功的管道运行

注意

由于源代码现在由 CodePipeline 生成,并且在 Terraform 代码中,我们将使用仓库的文件路径,并看到一个名为 src(这是 CodePipeline 生成的目录名称)的集群被构建。我们应该使用变量或本地值来改变 Terraform 生成集群名称的方式。

现在我们已经快速回顾了如何使用 CodePipeline 和 CodeBuild 根据代码的更改构建我们的集群,接下来我们来看如何使用 ArgoCD 和 Crossplane 以类似的方式部署 EKS 工作负载。

使用 ArgoCD、Crossplane 和 GitOps 部署工作负载

在上一节中,我们使用 CodePipeline 根据提交和 CodePipeline 配置将更改部署到我们的 AWS 环境。

GitOps 是实现 持续部署CD)的一种方式,用于同时部署容器化应用程序和基础设施,但更侧重于自服务和开发人员体验。这意味着开发人员不仅可以使用 Git 仓库来存储、版本控制、测试和构建他们的代码,还可以对他们的基础设施代码进行同样的操作,同时部署两者。

本章将使用两个开源项目——ArgoCD,这是一个部署工具,它会持续轮询我们的应用程序仓库以查找变化,以及K8s API来部署它们。Crossplane 允许我们使用自定义的 Kubernetes 资源来构建支持我们应用程序的基础设施资源,如数据库。ArgoCD 可以使用 Helm 来部署和修改(补丁)K8s 资源或 Kustomize。Kustomize 使你能够轻松定制 K8s 清单文件,并且也可以直接通过 kubectl 工具使用。使用的架构如下所示。

图 19.18 – GitOps 架构

图 19.18 – GitOps 架构

我们将使用通过 Terraform BluePrint 创建的集群,该集群已安装并运行 ArgoCD,因此我们将从 ArgoCD 仓库配置开始。

设置我们的应用程序仓库

我们将创建并克隆一个名为 myapp 的新 CodeCommit 仓库,使用在 为 Terraform 定制和版本控制 EKS 蓝图 部分中显示的相同命令,将仓库创建并克隆到我们的 Cloud9 实例中。

我们还应该在本地环境中安装 Kustomize 进行本地测试,使用以下命令:

(master) $ curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | bash
kustomize installed to /..
(master) $ kustomize version
v5.0.3

现在我们已经安装了仓库和 Kustomize 工具,可以设置一般结构。我们将使用暂停容器镜像,并根据环境调整命名空间、副本数和内存请求大小。

我们将使用两个清单文件,这些文件一旦创建,只有在添加或删除资源时才需要更改。namespace.yaml 文件将定义命名空间;示例如下:

apiVersion: v1
kind: Namespace
metadata:
  name: app

deployment.yaml 文件将定义暂停容器的部署。示例如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: inflate
  namespace: app
spec:
  replicas: 0
  selector:
    matchLabels:
      app: inflate
  template:
    metadata:
      labels:
        app: inflate
    spec:
      containers:
        - name: inflate
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
          resources:
            requests:
              memory: 1Gi

接下来,我们将创建 base 目录和 kustomize.yaml 文件,这些文件将引用前面的模板,并使用 kubectl create -k 命令进行部署的干运行。命令如下所示:

(master) $ mkdir base
(master) $ cd base
(master) $ touch namespace.yaml
(master) $ touch deployment.yaml
(master) $ touch kustomization.yaml
(master) $ kubectl create -k . --dry-run=client
namespace/app created (dry run)
deployment.apps/inflate created (dry run)

我们已经讨论了命名空间和部署文件,但 kustomize.yaml 文件也被 Kustomize 用来了解它需要部署或修改(补丁)哪些资源。示例如下:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - namespace.yaml
  - deployment.yaml

由于此文件是在基础目录中创建的,它仅引用两个没有修改的清单文件。接下来我们将创建两个覆盖文件,分别调整这些文件在非生产和生产环境中的值:

(master) $ mkdir -p ../overlays/production
(master) $ mkdir -p ../overlays/non-production
(master) $ touch ../overlays/production/kustomization.yaml
(master) $ touch ../overlays/non-production/kustomization.yaml
(master) $ touch ../overlays/non-production/deployment.yaml
master) $ touch ../overlays/production/deployment.yaml

非生产环境的 kustomize.yaml 文件如下所示,它将调整命名空间以及 non-production- 前缀应用到所有资源:

resources:
- ../../base
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: non-production
namePrefix: non-production-
patches:
- path: deployment.yaml

它还引用了本地目录中的 deployment.yaml 文件,该文件将基模板中的副本数增加到 1,并添加了新的限制和请求:

apiVersion: apps/v1
kind: Deployment
….
spec:
  replicas: 1
……
      containers:
        - name: inflate
…….
          resources:
            limits:
              memory: 1250Mi
            requests:
              memory: 1250Mi

当我们运行 kubectl create -k 命令时,这些更改将与基础清单合并并部署。以下命令将部署并验证我们针对非生产环境的自定义内容:

(master) $ pwd
../myapp/overlays/non-production
(master) $ kubectl create -k .
namespace/non-production created
deployment.apps/non-production-inflate created
(master) $ kubectl get all -n non-production
NAME     READY   STATUS    RESTARTS   AGE
pod/non-production-inflate-123   1/1     Running   0  13s
NAME  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/non-production-inflate   1/1     1    1 13s
NAME    DESIRED   CURRENT   READY   AGE
replicaset.apps/non-production-inflate-12   1 1 1 13s
(master) $ kubectl get po non-production-inflate-123 -n non-production -o json | jq -r '.spec.containers[].resources'
{
  "limits": {
    "memory": "1250Mi"
  },
  "requests": {
    "memory": "1250Mi"
  }

现在,我们可以将配置复制到 ./overlays/production 目录,修改前缀和命名空间为 production,将限制和请求更改为 2Gi,副本数更改为 3。我们现在可以将这些更改提交到我们的仓库,并且我们知道,如果从生产或非生产的 overlays 目录运行 Kustomize 命令,我们将为每个环境获得略有不同的配置。

下一步是配置 ArgoCD 来部署这些资源。

设置 ArgoCD 应用程序

ArgoCD 使用 应用程序 概念,代表一个 Git 仓库。根据应用程序的配置,ArgoCD 将轮询该仓库,在我们的情况下,使用 Kustomize 添加、修改或删除资源。

ArgoCD 不支持 AWS IAM 角色,因此它将使用 SSH 凭证来轮询仓库。所以,我们需要为具有访问 codecommit 仓库权限的 CI/CD 用户配置 SSH 凭证。我们将使用以下链接中的说明:docs.aws.amazon.com/codecommit/latest/userguide/setting-up-ssh-unixes.html 来创建 SSH 密钥,并将其添加到具有 CodeCommit 权限的用户中。一旦我们拥有 SSH 密钥 ID,我们可以执行以下操作:

  • 安装并配置 ArgoCD 客户端

  • 为 ArgoCD 添加一个秘密,用于连接到仓库

  • 添加我们的应用程序并检查部署

以下命令将安装 ArgoCD 客户端:

(master) $ sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
(master) $ rm argocd-linux-amd64
(master) $ argocd version
argocd: v2.7.2+cbee7e6
  BuildDate: 2023-05-12T14:06:49Z
  GitCommit: cbee7e6011407ed2d1066c482db74e97e0cc6bdb
  GitTreeState: clean
  GoVersion: go1.19.9
  Compiler: gc
  Platform: linux/amd64
FATA[0000] Argo CD server address unspecified

接下来,我们将配置必要的环境变量以连接到我们的环境;以下是示例,但你应该根据自己的环境添加相关细节:

ARGOCD_SERVER=$(kubectl get svc argo-cd-argocd-server -n argocd -o json | jq --raw-output '.status.loadBalancer.ingress[0].hostname')
(master) $ ARGOCD_PWD=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 - (master) $ GITOPS_IAM_SSH_KEY_ID=APKARDV7UN6242ZX
(master) $ AWS_DEFAULT_REGION=eu-central-1
Admin:~/environment/myapp (master) $ APP_REPO_NAME=myapp
(master) $ GITOPS_REPO_URL=ssh://${GITOPS_IAM_SSH_KEY_ID}@git-codecommit.${AWS_DEFAULT_REGION}.amazonaws.com/v1/repos/${APP_REPO_NAME}
(master) $ echo $GITOPS_REPO_URL > ./argocd_repo_url
(master) $ cat ./argocd_repo_url
ssh://APKARDV7UN6242ZX@git-codecommit.eu-central-1.amazonaws.com/v1/repos/myapp
(master) $ argocd login $ARGOCD_SERVER --username admin --password $ARGOCD_PWD --insecure
'admin:login' logged in successfully
Context 'a4e22bc700a154a13af063e8abe72c22-1646159678.eu-central-1.elb.amazonaws.com' updated

现在,我们可以使用以下命令将仓库和 SSH 密钥添加到 ArgoCD:

(master) $ argocd repo add $(cat ./argocd_repo_url) --ssh-private-key-path ${HOME}/.ssh/argocd --insecure-ignore-host-key --upsert --name myapp
Repository 'ssh://APKARDV7UN6242ZX@git-codecommit.eu-central-1.amazonaws.com/v1/repos/myapp' added
(master) $ argocd repo list
TYPE  NAME  INSECURE  OCI LFS  CREDS  STATUS  MESSAGE  PROJECT
git   myapp  ssh://APKARDV7UN6242ZX@git-codecommit.eu-central-1.amazonaws.com/v1/repos/myapp  true      false  false  false  Successful

我们可以设置应用程序,使用仓库和私钥来部署资源。我们将其指向非生产叠加层,以便它使用位于该位置的 Kustomize 配置:

(master) $ argocd app create myapp --repo $(cat ./argocd_repo_url) --path overlays/non-production --dest-server https://kubernetes.default.svc --sync-policy automated --self-heal --auto-prune
application 'myapp' created
(master) $ argocd app list | grep myapp
argocd/myapp  https://kubernetes.default.svc   default  Synced     Healthy  Auto-Prune  <none>      ssh://APKARDV7UN6242ZX@git-codecommit.eu-central-1.amazonaws.com/v1/repos/myapp  overlays/non-production
(master) $ kubectl get all -n non-production
NAME     READY   STATUS    RESTARTS   AGE
pod/non-production-inflate-22   1/1     Running   0    24s
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/non-production-inflate   1/1  1  1    25s
NAME   DESIRED   CURRENT   READY   AGE
replicaset.apps/non-production-inflate-22   1  1  1     25s

如果我们查看 Argo CD 的 UI,我们可以看到应用程序是健康的,组件已经部署,并且 ArgoCD 将继续同步它们,因为我们对底层的 CodeCommit 仓库进行了更改。

图 19.19 – ArgoCD 中 myapp 应用程序的状态

图 19.19 – ArgoCD 中 myapp 应用程序的状态

现在,我们有一个可以持续由 Argo CD 部署的应用程序。我们可以看到如何将基础设施资源添加到同一仓库,并让 Crossplane 为其提供服务。

使用 Crossplane 添加 AWS 基础设施

在本书中,我们展示了如何添加 K8s 资源并使用 K8s 控制器,如 AWS 负载均衡器控制器,来创建 AWS 资源,如网络负载均衡器或应用负载均衡器。Crossplane 可以看作是 AWS 资源的通用控制器。

我们将使用我们通过 Blueprints 创建的集群,但用最新版本替换 Crossplane 部署。因此,我们将安装 helm,然后使用它来部署 Crossplane 图表:

$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh
$ helm repo add Crossplane-stable https://charts.Crossplane.io/stable
$ helm install Crossplane --create-namespace --namespace Crossplane-system Crossplane-stable/Crossplane

注意

在部署之前,您可能需要删除Crossplane-system命名空间。

现在我们已经安装了最新版本的 Crossplane,我们需要配置提供程序及其相关权限。

设置我们的 Crossplane AWS 提供程序

由于 Crossplane 将在 AWS 中创建资源,它需要一个角色/权限来执行此操作。我们将从创建一个 IRSA 角色开始,将其映射到我们的集群,并赋予它管理员角色。相关命令如下所示:

$ account_id=$(aws sts get-caller-identity --query "Account" --output text)
$ oidc_provider=$(aws eks describe-cluster --name src --region $AWS_DEFAULT_REGION --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")
$ cat > trust.yaml <<EOF
{ "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${account_id}:oidc-provider/${oidc_provider}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "${oidc_provider}:sub": "system:serviceaccount:Crossplane:system:provider-aws-*"
        }}}]}
EOF
$ aws iam create-role --role-name bespoke-Crossplane --assume-role-policy-document file://trust.json --description "Crossplane IRSA role"
{
    "Role": {
        "Path": "/",
        "RoleName": "bespoke-Crossplane",
        "RoleId": "AROARDV7UN62754DFZQBL",
        "Arn": "arn:aws:iam::112233:role/bespoke-Crossplane",
….
$ aws iam attach-role-policy --role-name bespoke-Crossplane --policy-arn=arn:aws:iam::aws:policy/AdministratorAccess

现在,我们有了一个信任我们集群的 OIDC 提供程序并具有创建 AWS 资源权限的角色。接下来,我们需要配置 Crossplane 部署来使用它。可以使用以下清单来配置提供程序和控制器:

apiVersion: pkg.Crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
  name: aws-config
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::112233:role/bespoke-Crossplane
spec:
  podSecurityContext:
    fsGroup: 2000
  args:
    - --debug
---
apiVersion: pkg.Crossplane.io/v1
kind: Provider
metadata:
  name: provider-aws
spec:
  package: xpkg.upbound.io/upbound/provider-aws:v0.27.0
  controllerConfigRef:
    name: aws-config

我们可以使用以下命令部署 AWS 提供程序:

$ kubectl create -f Crossplane-provider.yaml
controllerconfig.pkg.Crossplane.io/aws-config created
provider.pkg.Crossplane.io/provider-aws created
$ kubectl get providers
NAME  INSTALLED   HEALTHY   PACKAGE             AGE
provider-aws   True        True      xpkg.upbound.io/upbound/provider-aws:v0.27.0   52m

一旦提供程序是健康的,我们可以通过添加提供程序配置并将凭证插入方法定义为 IRSA 来完成配置。这是 upbound AWS 提供程序的区别之一——它使用不同的 API 和 IRSA 源密钥:

apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: provider-aws
spec:
  credentials:
    source: IRSA

我们可以使用以下命令部署此清单文件:

$ kubectl create -f ub-config.yaml
providerconfig.aws.upbound.io/provider-aws created

由于我们启用了调试日志记录,可以使用以下命令查看提供程序的日志,以确认所有配置和 AWS 权限是否已正确设置:

$ kubectl get po -n Crossplane-system
NAME  READY   STATUS    RESTARTS      AGE
Crossplane-12     1/1     Running   1 (57m ago)   66m
Crossplane-rbac-manager-12    1/1     Running   0   66m
provider-aws-12   1/1     Running   1 (58m ago)   60m
$ k logs provider-aws-12 -n Crossplane-system
….
1.6848748355755348e+09  DEBUG   provider-aws    Reconciling     {"controller": "providerconfig/providerconfig.aws.upbound.io", "request": "/provider-aws"}

注意

对于生产配置,您应该禁用调试日志记录,因为它非常冗长,并且会生成大量数据。

创建基础设施资源

现在我们已经有了一个工作中的 Crossplane AWS 提供程序,我们实际上可以配置一个 AWS 资源。我们将配置一个带有一些基本配置的 S3 桶。以下清单将创建一个名为myapp-Crossplane-bucket637678的 S3 桶,并使用我们在上一步创建的 AWS 提供程序:

apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
  name: myapp-Crossplane-bucket637678
spec:
  forProvider:
    region: eu-central-1
  providerConfigRef:
    name: provider-aws

我们可以使用以下命令来部署并验证桶:

$ kubectl create -f Crossplane-us3.yaml
bucket.s3.aws.upbound.io/myapp-Crossplane-bucket637678 created
$ kubectl get bucket
NAME   READY   SYNCED   EXTERNAL-NAME                   AGE
myapp-Crossplane-bucket637678   True    True     myapp-Crossplane-bucket637678   15s
$ aws s3 ls | grep Crossplane
2023-05-23 20:57:39 myapp-Crossplane-bucket637678
$ kubectl get  bucket myapp-Crossplane-bucket637678 -o json | jq .status.atProvider.arn
"arn:aws:s3:::myapp-Crossplane-bucket637678"

此清单可以添加到我们的应用程序仓库,并修改相关的 kustomize.yaml 文件。这意味着,作为 DevOps 工程师或开发人员,我们不仅可以配置应用程序,还可以配置任何支持的基础设施。如果您想使用 ArgoCD 部署 Crossplane 资源,请参阅此链接:https://docs.Crossplane.io/v1.10/guides/argo-cd-Crossplane/。

尽管这是一个较长的章节,但我只是触及了开发 EKS 的表面,但希望您已经掌握了足够的信息,可以进一步探索!

在本节中,我们探讨了如何在 EKS 上开发,使用多种 AWS 服务和开源工具来自动化集群构建、部署和测试应用。现在,我们将回顾本章的关键学习点。

总结

在这一章中,我们首先考虑了需要在 EKS 上进行开发的多种角色,从传统开发者到 DevOps 或平台工程师。这些角色需要类似但不同的需求来完成工作,因此在进行 EKS 开发时,考虑操作模式是非常重要的。

接下来,我们了解了如何使用 IDE 开发基础设施/应用代码,以及 AWS Cloud9 如何提供一个简单且安全的界面来在 EKS 上执行这些操作。然后我们创建了一个 CodeCommit 仓库,并使用 Terraform 命令部署它。这创建了一个完整的 EKS 集群,位于新的 VPC 中,并自动配置了一系列应用和插件。

然后我们探讨了平台/DevOps 工程师如何使用 buildspec.yaml 文件自动化部署/测试 EKS 集群,我们通过 CodeCommit 分支实现了此过程的自动化。此外,我们还探讨了 DevOps 工程师或开发者如何使用 ArgoCD/Kustomize 自动化 K8s 清单文件的自定义和部署。

最后,我们探讨了如何通过使用 Crossplane 将 AWS 基础设施资源集成到我们的应用程序仓库中,并通过 K8s 清单和自定义资源在 AWS 中创建一个 S3 存储桶。

在最后一章中,我们将讨论如何排查常见的 EKS 问题。

深入阅读

  • 在无头模式下使用 Cloud9:

https://aws.amazon.com/blogs/devops/how-to-run-headless-front-end-tests-with-aws-cloud9-and-aws-codebuild/

  • 开始使用 Terraform 的 EKS 蓝图:

aws-ia.github.io/terraform-aws-eks-blueprints/getting-started/

  • 创建安全的 AWS CI/CD 管道:

https://aws.amazon.com/blogs/devops/setting-up-a-secure-ci-cd-pipeline-in-a-private-amazon-virtual-private-cloud-with-no-public-internet-access/

  • 在 AWS 上使用 GitOps:

aws.amazon.com/blogs/containers/gitops-model-for-provisioning-and-bootstrapping-amazon-eks-clusters-using-Crossplane-and-argo-cd/

第五部分:克服常见的 EKS 挑战

本节的目标是提供更多关于排查常见 EKS 问题的细节。

本节包括以下章节:

  • 第二十章**,排查常见问题

第二十章:故障排除常见问题

在本书中,我们创建了集群并添加了控制器、附加组件、工作负载等,但并不是一切都按计划进行。虽然 K8s 在易用性方面做得很好,但你可能会在遵循本书或日常工作中遇到至少一个问题。

理解如何进行故障排除以及使用哪些工具来识别根本原因并希望解决它们,是使用或运行弹性 Kubernetes 服务EKS)的重要组成部分。在本章中,我们将介绍与 EKS 一起使用的常见技术和工具,以及在使用 Amazon EKS 时可能遇到的常见错误。

在本章中,我们将讨论开始使用 Amazon EKS 时的一些常见问题,逐步介绍细节,并学习如何进行故障排除,例如以下内容:

  • 常见的 K8s 工具/技术用于故障排除 EKS

  • 常见的集群访问问题

  • 常见节点/计算问题

  • 常见的 Pod 网络问题

  • 常见工作负载问题

让我们开始吧!

技术要求

读者应对 YAML、AWS IAM 和 EKS 架构有一定了解。在开始本章之前,请确保以下内容:

  • 你可以访问你的 EKS 集群 API 端点的网络连接

  • AWS CLI、Docker 和 kubectl 二进制文件已安装在你的工作站上,并且具有管理员访问权限

常见的 K8s 工具/技术用于故障排除 EKS

任何故障排除过程都始于理解问题,并区分症状和根本原因。症状常常会被误认为是根本原因,因此故障排除过程通常是反复的,伴随着不断的测试和观察,专注于实际问题而忽略症状和误报。

一旦理解了问题,接下来就是理解如何缓解、解决或忽略它。并不是所有问题都能立即解决,因此你可能需要一个策略暂时绕过它们。

最终阶段将是问题的解决/修复。这可能需要更新你的集群、应用代码,或者两者都需要更新,具体取决于问题的性质,你管理的集群数量以及对用户/客户的影响,这可能是一个相当复杂的过程。

通常,当我试图理解问题时,我使用以下检查清单:

  • 有什么变化? – 大多数支持工程师的座右铭;是否进行了新的部署?集群或附加组件升级了?AWS/本地基础设施发生了变化吗?接下来我们将讨论工具,但记录任何变化(至少在生产环境中)是一个良好的实践,可以帮助工程师确定在这三个领域中发生了什么变化。

  • 调查影响 – 在这里,你需要观察症状。问题表现为何?是处理速度变慢还是完全失败?问题的范围是什么?仅限于某个命名空间?某个集群?某个 VPC?结合对变化的理解,这通常可以帮助工程师将问题缩小到根本原因,但可能需要你通过多个假设进行迭代。是集群中新添加的节点类型导致的吗?不,是节点上的新部署!或者,是部署中的应用程序库导致的! |

  • 规划修复或缓解措施 – 你能轻松修复问题且不会产生影响,还是需要规划变更?制定计划并与团队、应用所有者以及任何客户代表讨论。如果你能够在非生产环境中复现问题和修复过程,那么在进行任何生产变更之前,应该这么做,这有助于整体计划的执行。

现在我们已经基本理解了你通常会经历的故障排除步骤。接下来,让我们看看一些你可能用来进行故障排除的工具。

常见的 EKS 故障排除工具

以下表格列出了常见的故障排除工具;它并不是详尽无遗的,因为 K8s 生态系统庞大且有很多贡献者,但这些是我使用过的或者曾经使用过的工具。(如果你的最爱没有包括在内,请见谅!)

阶段 工具 描述
发生了什么变化? AWS CloudTrail CloudTrail 可以提供你在 AWS 账户中所做的每一个 API 调用的日志,包括 AWS EKS API 调用(不是 K8s API)、负载均衡器、IAM 等的 API 调用。
AWS CloudWatch 提供控制面板和日志,用于控制平面和(可选的)数据平面。
Grafana Loki grafana.com/oss/loki/ 一个开源的日志聚合工具。
kubectl diff kubectl diff 允许你比较本地清单文件与运行中的配置,查看将会发生(或已经发生)哪些变化。如果你能访问以前的清单文件或提交记录,这是一个非常有用的工具。
调查影响 Linux 命令行工具 任何标准的 Linux 工具,如 digpingtcpdump 等,都应该用于确定问题的范围和影响。
kubectl kubectl describekubectl get eventskubectl logs 是任何故障排除过程中的基本命令。kubectl top 可用于查看 Pod 和 Node 层级的统计信息(前提是已安装度量服务器)
Ktop: github.com/vladimirvivien/ktop 一个用于集群管理的有用 CLI 工具
k9s: github.com/derailed/k9s 一个用于集群管理的有用 CLI 工具
AWS Log Collectorgithub.com/awslabs/amazon-eks-ami/tree/master/log-collector-script/linux 一个 AWS 支持脚本,用于收集操作系统和 K8s 日志。

表 20.1 – 常见的 EKS 故障排除工具

注意

你现在还可以使用kubectl debug将故障排除容器注入到运行中的 Pod 中,以便在 K8s 1.25 及以上版本中进行实时调试。

除了技术工具/服务,AWS 还提供了多种支持服务,这些服务在此列出。

AWS Premium 支持

AWS 为所有客户提供技术支持服务,并提供 365 天全天候 24/7 的服务。该服务旨在为你提供合适的工具和 AWS 专业知识的访问权限,让你可以专注于业务增长,同时优化系统性能,学习如何减少不必要的基础设施成本,并在使用每个 AWS 服务时减少故障排除的复杂性。

支持计划包括不同级别,例如开发者商业企业。根据你选择的支持计划,你可以通过电子邮件、电话甚至在线聊天与 AWS 专家联系,寻求有关技术或操作问题的咨询,使用 AWS 支持案例控制台。

注意

如果你对 AWS Premium 支持计划感兴趣,并想了解更多详细信息,可以查看他们的服务功能页面:aws.amazon.com/premiumsupport/plans/

支持模型的一部分是访问知识中心,其中包含了关于所有 AWS 平台产品(不仅仅是 EKS)的大量附加信息。

AWS 支持知识中心

AWS 支持知识中心由 AWS 维护,是一个集中资源中心,帮助你更好地学习如何修复使用 AWS 服务时的操作问题或配置问题。它列出了 AWS 支持从客户那里收到的最常见问题和请求。你可以通过访问以下链接查看更多详情:aws.amazon.com/premiumsupport/knowledge-center/

你可以查看 Amazon EKS 部分,了解 Amazon EKS 中常见的问题。有些文章还包括完整的逐步教程的视频。现在我们已经看了一些常见的故障排除工具,接下来我们来看看一些典型的集群访问问题。

常见的集群访问问题

本节列出了访问 EKS 集群时常见的问题。接下来的几节将描述常见的症状以及如何修复它们。

你无法使用 kubectl 访问你的集群

通过kubectl与集群 API 交互,或将其作为 CI/CD 管道或工具的一部分,是使用 EKS 的关键步骤!接下来列出了一些常见问题,并附上了可能的解决方案。

注意

虽然接下来显示的错误可能指向一个特定问题,但它们也可能与这里未记录的其他问题有关。

在显示的错误信息中,shell 没有配置环境变量或~/.aws目录中的配置文件凭据:

$ kubectl get node
Unable to locate credentials. You can configure credentials by running "aws configure".
$ aws eks update-kubeconfig --name mycluster  --region eu-central-1
Unable to locate credentials. You can configure credentials by running "aws configure".

您还可能会看到以下错误消息,虽然不同,但意思相同——该 shell 没有将凭证配置为环境变量或 ~/.aws 目录中的配置文件:

$ kubectl get node
Unable to locate credentials. You can configure credentials by running "aws configure".

在下文所示的错误消息中,shell 中配置的凭证没有正确的 IAM 权限或 K8s 权限:

$ kubectl get node
error: You must be logged in to the server (Unauthorized)

在此错误消息中,最可能的问题是网络连接问题,客户端对私有 EKS 集群的网络访问受限,或者存在 IP 白名单。这个错误有时会在使用没有相关访问权限的 IAM 角色时出现:

$ kubectl get node
Unable to connect to the server: dial tcp 3.69.90.98:443: i/o timeout

现在我们已经查看了一些常见的集群访问问题,让我们看看一些典型的节点/计算问题。

常见的节点/计算问题

本节列出了你可能会在 EKS 集群中看到的工作节点的常见问题,并提供了可能的解决方案。

节点/节点无法加入集群

对于这个问题,发生的变化(例如添加了新节点或更改了 IP 白名单)将决定需要修复的内容。通常,错误仅仅是节点处于 NotReady/Unknown 状态,如下所示,这有许多不同的根本原因:

$ kubectl get node
NAME  STATUS   ROLES    AGE    VERSION
ip-172-31-10-50.x.internal    NotReady    <none>   7d4h   v1.24.13-eks-0a21954
ip-172-31-11-89.x.internal    Unknown    <none>   7d4h   v1.24.13-eks-0a21954

如果你正在运行自管理节点,EC2 是否执行了 bootstrap.sh 脚本?对于托管节点,这将自动发生,但如果没有,您的 EC2 实例将不会自动注册。

如果您在公共集群上有 IP 白名单,是否将您的节点的公共地址或 NAT 网关添加到了白名单中?如果没有,您的节点将无法与 K8s 集群通信。

对于私有或私有/公共集群,您的工作节点安全组是否已经与集群的安全组注册?如果没有,您的节点将无法与 K8s 集群通信。

Dec 23 17:42:36 ... kubelet[92039]: E1223 17:42:36.551307 92039 kubelet.go:2240] node "XXXXXXXXX" not found

在前面从 kubelet 日志中显示的错误消息中,最可能的问题是 VPC DNS 问题,其中 VPC 尚未配置 DNS 主机名或解析。

Error: Kubernetes cluster unreachable: Get "https://XXXXXXXXXX.gr7.eu-central-1.eks.amazonaws.com/version?timeout=32s": dial tcp <API_SERVER_IP>:443: i/o timeout

在前面从 kubelet 日志中显示的错误消息中,最可能的问题是网络问题,其中安全组、NACL、NAT 网关或端点配置错误或不存在。

Jan 01 12:23:01 XXXXXXXXX kubelet[4445]: E1012 12:23:01.369732    4445 kubelet_node_status.go:377] Error updating node status, will retry: error getting node "XXXXXXXXX": Unauthorized

在前面从 kubelet 日志中显示的错误消息中,最可能的问题是 IAM 身份验证问题,其中节点的 IAM 角色没有被添加到 aws-auth 配置映射中。

onditions:Type   Status  LastHeartbeatTime  LastTransitionTime     Reason          Message
  MemoryPressure   False   Tue, 12 Jul 2022 03:10:33 +0000   Wed, 29 Jun 2022 13:21:17 +0000   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure     True    Tue, 12 Jul 2022 03:10:33 +0000   Wed, 06 Jul 2022 19:46:54 +0000   KubeletHasDiskPressure       kubelet has disk pressure

在前面从 describe node 命令中显示的错误消息中,最可能的问题是由于磁盘空间不足引起的磁盘问题(通常是由于日志过度控制)。现在我们已经看过一些常见的集群计算问题,让我们看看一些典型的网络问题。

常见的 Pod 网络问题

在前面的部分中,我们已经讨论了在 API/控制平面和工作节点环境中的网络问题。本节列出了你可能会在 EKS 集群中看到的 Pod 网络问题,并提供了可能的解决方案。

* connect to 172.16.24.25 port 8080 failed: Connection timed out
* Closing connection 0
curl: (7) Failed to connect to 172.16.24.25 port 8080 failed: Connection timed out

从 pod 日志中显示的错误消息来看,最可能的问题是工作节点(或 pod)的安全组配置问题。请检查工作节点的安全组配置,确保允许所有必要的端口、IP CIDR 范围和其他安全组(包括其自身)。如果你使用的是 NACL,确保允许外部临时端口,并且任何入口端口也被允许。

Error: RequestError: send request failed caused by: Post dial tcp: i/o timeout

从 pod 日志中显示的错误消息来看,最可能的问题是 DNS 问题。请确保clusterDNSCoreDNS正常工作,并且 VPC 已启用 DNS 解析和主机名功能。

Error: RequestError: send request failed caused by: Post dial tcp 172.16.24.25:443: i/o timeout

从 pod 日志中显示的前述错误消息来看,最可能的问题是连接问题。

Failed create pod mypod: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod network: add cmd: failed to assign an IP address to container

pod describe命令显示的前述错误消息显示 pod 停留在ContainerCreating状态。最可能的问题是 VPC 中没有更多可用的 IP 地址。如果没有使用前缀地址分配,EC2 实例类型可能已经用尽了它能够支持的 IP 地址数量。

NAME    READY     STATUS             RESTARTS    AGE
aws-node-bbwpq   0/1     CrashLoopBackOff     12         51m
aws-node-nw7v8  0/1     CrashLoopBackOff     12         51m
coredns-12     0/1     Pending         0         54m
coredns-13     0/1     Pending         0         54m

get pod命令显示的错误消息来看,最可能的问题是 VPC 或安全组问题,这会对 pod 中的 CNI 或 DNS 操作产生连锁反应。

"msg"="Reconciler error" "error"="failed to build LoadBalancer configuration due to retrieval of subnets failed to resolve 2 qualified subnets.

describe deployment命令中的错误消息来看,最可能的问题是某个子网没有被标记为内部或外部负载均衡器,因此无法被发现。现在我们已经看了一些常见的 pod 网络问题,接下来我们来看一些典型的工作负载问题。

常见的工作负载问题

本节列出了你可能在 EKS 集群中遇到的与 pod 和/或部署相关的常见问题,以及可能的解决方案。

State:  Running
Started: Sun, 16 Feb 2020 10:20:09 +0000
Last State: Terminated
Reason: OOMKilled

kubectl describe pod命令显示的前述错误消息中,最可能的问题是连接问题或内存问题,因为OOMKilled意味着 pod 达到了其内存限制,因此会重启。你需要在部署或 pod 配置中增加内存设置。

NAME            READY  STATUS            RESTARTS   AGE
myDeployment1-123... 1/1    Running           1     17m
myDeployment1-234... 0/1    CrashLoopBackOff  2     1m

kubectl get po命令中的错误消息来看,可能存在多个问题,无论是由于 DockerFile 错误、拉取镜像文件失败等原因。运行kubectl logs命令以获取更多有关错误原因的信息。

  Warning  FailedScheduling  22s (x14 over 13m)  default-scheduler  0/3 nodes are available: 3 Insufficient cpu.

kubectl describe po命令显示的错误消息中,pod 处于Pending状态。最可能的问题是你的工作节点的 CPU 资源不足。

  Warning  FailedScheduling  80s (x14 over 15m)  default-scheduler  0/3 nodes are available: 3 Insufficient memory.

kubectl describe po命令的错误消息显示 pod 处于Pending状态,最可能的问题是工作节点的内存资源不足。

    State:          Waiting
      Reason:       ErrImagePull

kubectl describe po命令显示的前述错误消息中,pod 处于Pending状态。最可能的问题是容器镜像不正确、不可用,或者托管在未在工作节点中配置的私有仓库中。

Warning  FailedScheduling  77s (x3 over 79s)  default-scheduler  0/1 nodes are available: 1 node(s) had taint {node-typee: high-memory}, that the pod didn't tolerate.

来自kubectl describe po命令的前面错误信息显示 Pod 处于 Pending 状态。最可能的问题是 Pod 不匹配相应的节点容忍度。本章不会涉及你在 K8s/EKS 中可能遇到的所有症状和根本原因,但希望你已经获得足够的信息,能够应对常见的问题。

在本节中,我们查看了用于故障排除 EKS 的工具和技术以及你可能遇到的常见问题。现在,我们将回顾这一章的关键学习要点。

总结

在这一章中,我们首先看了一种通用的故障排除方法,并介绍了一些常用工具,这些工具可以帮助我们确定发生了什么变化以及问题的范围/影响。

然后我们转向了在连接到 EKS 集群、计算节点、Pod 网络和工作负载时可能遇到的常见问题/症状,并使用kubectl命令来识别这些问题,并提供一些可能的解决方案。

这是最后一章,到现在为止,你应该已经掌握了构建和管理 EKS 集群以及运行在其上的工作负载所需的所有知识。

恭喜你完成了本书的学习!希望你发现它既有信息量又有实用性。

进一步阅读

  • 调试 K8s 工具:

www.cncf.io/blog/2022/09/15/10-critical-kubernetes-tools-and-how-to-debug-them/

  • EKS 官方故障排除指南:

docs.aws.amazon.com/eks/latest/userguide/troubleshooting.html

posted @ 2025-06-30 19:29  绝不原创的飞龙  阅读(3)  评论(0)    收藏  举报