Kubernetes-生产环境最佳实践-全-

Kubernetes 生产环境最佳实践(全)

原文:annas-archive.org/md5/5cc672682018ad75f1f334c1efca7664

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Kubernetes 是一个开源容器编排平台,最初由 Google 开发,并于 2014 年对公众开放。Kubernetes 的普及帮助开发者更轻松地管理基于容器的复杂分布式系统的部署。自诞生以来,社区围绕 Kubernetes 建立了一个庞大的生态系统,许多开源项目使得管理功能的自动化成为可能。

本书专为 Kubernetes 管理员和 站点可靠性工程师 (SREs) 设计,旨在快速帮助他们根据行业最佳实践以及来自大规模 Kubernetes 部署早期技术采用者的成熟技术,构建和管理生产级 Kubernetes 基础设施。

虽然我们使用 Amazon 弹性 Kubernetes 服务 (EKS) 来提供本书中的实际练习,但我们认为,所解释的 Kubernetes 设计、部署和配置概念与技术对其他云服务提供商仍然适用。在选择部署和配置工具时,我们决定使用如 Terraform 和 Ansible 等云无关工具,以确保跨云服务提供商的可移植性。

生产环境中的 Kubernetes 最佳实践 帮助你获得使用 Kubernetes 托管生产工作负载的信心,掌握构建集群所需的全面基础设施设计知识,并清楚地了解如何高效地管理和操作这些集群。

本书适用对象

本书非常适合那些具备 Kubernetes 基础知识,并愿意将云行业最佳实践应用于设计、构建和运营生产级 Kubernetes 集群的云基础设施架构师、SRE、DevOps 工程师、系统管理员和工程经理。

拥有基本的 Kubernetes、AWS、Terraform、Ansible 和 Bash 知识将是有益的。

本书的内容

第一章Kubernetes 基础设施与生产就绪性简介,教你 Kubernetes 基础设施的基本知识,然后解释基础设施设计的原则,最后讲解生产就绪集群的特性。

第二章构建生产级 Kubernetes 基础设施架构,教你在设计 Kubernetes 基础设施时需要考虑的各个方面、权衡以及最佳实践。

第三章使用 AWS 和 Terraform 部署 Kubernetes 集群,教你如何使用 AWS、Terraform 和基础设施即代码技术来部署 Kubernetes 基础设施。

第四章使用 Ansible 管理集群配置,教你如何使用 Ansible 为 Kubernetes 集群构建灵活且可扩展的配置管理解决方案。

第五章配置和增强 Kubernetes 网络服务,教你如何配置和改善 Kubernetes 集群的网络,并使用必需的 Kubernetes 网络插件。

第六章有效地保护 Kubernetes 安全,教你 Kubernetes 安全最佳实践,以及如何验证和确保集群的安全。

第七章管理存储和有状态应用程序,教你如何使用 Kubernetes 中最好的存储管理解决方案克服存储挑战。

第八章部署无缝且可靠的应用程序,教你容器和镜像的最佳实践,以及应用部署策略,以实现生产中的可扩展服务。

第九章监控、日志记录和可观测性,教你 Kubernetes 可观测性最佳实践,监控的重要指标,以及市场上可用的监控和日志记录技术栈,及其使用场景。

第十章高效操作和维护 Kubernetes 集群,教你 Kubernetes 操作的最佳实践,以及集群维护任务,如升级、轮换、备份和灾难恢复,和改善集群质量的解决方案。

最大化本书的价值

使用本书时,你需要访问计算机、服务器、AWS 或其他云服务提供商的服务,能够在这些服务上创建虚拟机实例。为了设置实验环境,你可能还需要更大的云实例,这些实例将需要你启用计费功能。

如果你使用的是本书的数字版,建议你自己输入代码,或者通过 GitHub 仓库访问代码(链接将在下一节提供)。这样做将帮助你避免因复制粘贴代码而引发的潜在错误。

下载示例代码文件

你可以从 GitHub 下载本书的示例代码文件,链接为 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices。如果代码有更新,它将在现有的 GitHub 仓库中更新。

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

代码实战

本书的《代码实战》视频可以在 bit.ly/36JpElI 查看。

下载彩色图片

我们还提供了一个 PDF 文件,包含本书中使用的截图/图表的彩色版本。您可以在此下载:static.packt-cdn.com/downloads/9781800202450_ColorImages.pdf

使用的约定

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

文本中的代码:表示文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号。示例如下:“作为最佳实践,我们建议将任何特权 Pod 限制在kube-system命名空间内。”

代码块的显示方式如下:

terraform {
  required_version = "~> 0.12.24"
}

当我们希望引起您对某段代码的注意时,相关的行或项目会以粗体显示:

provider "aws" {
  region = var.aws_region
  version = "~> 2.52.0"
}

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

$ cd Chapter03/terraform/shared-state
$ terraform init

粗体:表示新术语、重要词汇或屏幕上显示的词语。例如,菜单或对话框中的词汇会以这种方式显示。示例如下:“在管理面板中选择系统信息。”

提示或重要注意事项

如此显示。

联系我们

我们始终欢迎读者的反馈。

一般反馈:如果您对本书的任何内容有疑问,请在邮件主题中注明书名,并发送至 customercare@packtpub.com。

勘误:尽管我们已尽最大努力确保内容的准确性,但错误难免。如果您在本书中发现任何错误,恳请您报告给我们。请访问 www.packtpub.com/support/errata,选择您的书籍,点击“Errata Submission Form”链接,并填写相关信息。

盗版:如果您在互联网上发现我们的作品的非法复制形式,恳请您提供相关的地址或网站名称。请通过 copyright@packt.com 与我们联系,并附上链接。

如果您有意成为作者:如果您在某个主题方面具有专业知识,并且有兴趣写作或参与编写书籍,请访问 authors.packtpub.com

评论

请留下评论。在阅读和使用本书后,您为何不在购买平台上留下您的评论?潜在的读者可以看到并参考您的公正意见来做出购买决策,Packt 也能了解您对我们产品的看法,作者也能看到您对他们书籍的反馈。谢谢!

欲了解更多有关 Packt 的信息,请访问 packt.com

第一章:第一章:Kubernetes 基础设施与生产就绪简介

随着越来越多的组织采纳 Kubernetes 来管理基础设施,它正成为行业事实上的标准,用于在云端和本地环境中编排和管理分布式应用程序。

无论你是将公司应用迁移到云端的个人贡献者,还是领导云转型项目的决策者,你都应该规划 Kubernetes 的迁移之路,并了解其面临的挑战。

如果本书有一个核心目的,那就是引导你构建一个生产就绪的 Kubernetes 基础设施,同时避免常见的陷阱。这是我们撰写此主题的原因,因为我们在多年的 Kubernetes 集群建设和运营过程中见证了失败与成功。我们相信,你可以避免许多失败,节省时间和成本,提高可靠性,达成你的业务目标。

在本章中,你将学习如何使用最佳实践部署 Kubernetes 生产集群。我们将解释本书接下来要遵循的路线图,并解释设计和实施 Kubernetes 集群时常用的基础概念。理解这些概念及相关原则是构建和运营生产基础设施的关键。此外,我们还将设定你对本书内容范围的预期。

本书将讨论它将解决的核心问题,并简要涵盖诸如 Kubernetes 生产挑战、生产就绪特性、云原生环境以及基础设施设计与管理原则等主题。

本章将涵盖以下主题:

  • Kubernetes 基础设施的基础知识

  • 为什么 Kubernetes 在生产中具有挑战性

  • Kubernetes 生产就绪性

  • Kubernetes 基础设施最佳实践

  • 云原生方法

Kubernetes 基础设施的基础知识

如果你正在阅读本书,说明你已经决定将 Kubernetes 基础设施提升到一个更高级别,这意味着你已经超越了评估技术的阶段。要构建生产级基础设施,投资仍然是一个负担,并且仍然需要向企业和组织领导层提供有力的理由。我们将在本节中尽可能具体地说明为什么我们需要一个可靠的 Kubernetes 基础设施,并澄清你在生产环境中应该预见到的挑战。

Kubernetes 在全球范围内的采用正在迅猛增长,预计这种增长将继续增加,因为国际数据公司IDC)预测,到 2021 年,约 95% 的新微服务将部署在容器中。大多数公司发现,容器和 Kubernetes 有助于优化成本、简化部署和操作、缩短上市时间,并在混合云策略中发挥重要作用。类似地,Gartner 预测,到 2021 年,超过 70% 的组织将运行两个或更多容器化应用程序,而 2019 年这一比例不到 20%。

Kubernetes 组件

“Kubernetes(K8s)是一个开源系统,用于自动化容器化应用程序的部署、扩展和管理。”

– kubernetes.io

由于我们关注构建一个可靠的 Kubernetes 集群,我们将介绍 Kubernetes 集群架构及其组件的概述,然后你将了解生产中的挑战。

Kubernetes 具有分布式系统架构——特别是客户端-服务器架构。这里有一个或多个主节点,Kubernetes 在这些节点上运行控制平面组件。

Kubernetes 部署 pod 和工作负载的地方是工作节点。一个集群最多可以管理 5000 个节点。Kubernetes 集群架构如以下图所示:

图 1.1 – Kubernetes 集群架构

图 1.1 – Kubernetes 集群架构

上述图表展示了一个典型的高可用性 Kubernetes 集群架构,包括核心组件。它展示了 Kubernetes 各部分之间是如何相互通信的。虽然你已经对 Kubernetes 集群架构有了基本的了解,但我们仍然需要在接下来的章节中刷新这一知识,因为我们将在创建和调整集群配置时与这些组件进行更深入的互动。

控制平面组件

控制平面组件是构建 Kubernetes 主节点的核心软件部分。除 etcd 外,它们都属于 Kubernetes 项目,etcd 是一个独立的项目。这些组件遵循分布式系统架构,可以轻松地水平扩展以增加集群容量并提供高可用性:

  • kube-apiserver:API 服务器是集群组件的管理者,负责处理和提供管理 API,充当集群组件之间通信的中介。

  • etcd:这是一个分布式、高可用的键值数据存储,作为集群的核心支撑,存储着所有数据。

  • kube-controller-manager:它管理控制集群的控制器进程——例如,控制节点的节点控制器、控制部署的复制控制器,以及控制集群中暴露的服务端点的端点控制器。

  • kube-scheduler:该组件负责在各个节点之间调度 Pod。它根据调度算法、可用资源和位置配置决定哪些 Pod 运行在哪些节点上。

节点组件

节点组件是一组在每个工作节点上运行的软件代理,用于维护正在运行的 Pod,并提供网络代理服务以及容器的基础运行时环境:

  • kubelet:这是一个在集群中每个节点上运行的代理服务,它会定期获取一组 Pod 规格(描述 Pod 规格的 YAML 格式清单文件),并确保通过这些规格描述的 Pod 正常运行。此外,它还负责向主节点报告其运行的节点的健康状况。

  • kube-proxy:这是一个代理服务,在集群中的每个节点上运行,用于在节点上创建、更新和删除网络角色,通常使用 Linux iptables。 这些网络规则允许 Kubernetes 集群内外部的 Pod 之间进行通信。

  • containerd 用于运行容器,而 kubevirtvirtlet 用于运行虚拟机。

为什么 Kubernetes 在生产环境中充满挑战

Kubernetes 安装起来可能很简单,但操作和维护却非常复杂。Kubernetes 在生产环境中会带来许多挑战和困难,涵盖从扩展、正常运行时间和安全性,到弹性、可观察性、资源利用和成本管理等方面。Kubernetes 在解决容器管理和调度方面取得了成功,并在计算服务之上创建了一个标准层。然而,Kubernetes 仍然缺乏对一些关键服务的适当或完整支持,例如身份和访问管理IAM)、存储和镜像仓库。

通常,一个 Kubernetes 集群属于一个更大的公司生产基础设施的一部分,其中包括数据库、IAM、轻量级目录访问协议LDAP)、消息传递、流媒体等。将 Kubernetes 集群投入生产需要将其与这些外部基础设施连接起来。

即使是在云转型项目中,我们也希望 Kubernetes 管理并与本地基础设施和服务进行集成,这将生产环境的复杂性提升到一个新的层次。

另一个挑战出现在团队开始采用 Kubernetes 时,他们假设它能够解决应用程序的扩展性和正常运行时间问题,但通常没有规划好第二天的运维问题。这最终会导致在安全性、扩展性、正常运行时间、资源利用、集群迁移、升级和性能调优方面出现灾难性的后果。

除了技术挑战外,还有管理上的挑战,特别是在我们将 Kubernetes 应用于拥有多个团队的大型组织时,如果该组织未能做好准备,未能建立适当的团队结构来运营和管理 Kubernetes 基础设施,这可能会导致团队在标准工具、最佳实践和交付工作流上难以达成一致。

Kubernetes 生产就绪

“当你的产品超越客户预期,以支持业务增长的方式提供时,它就是生产就绪的。”

– 卡特·摩根(Carter Morgan),开发者倡导者,Google

生产就绪是我们在本书中需要实现的目标,尽管我们可能没有一个明确的定义来界定这个流行词。它可能意味着一个能够以可靠且安全的方式服务生产工作负载和真实流量的集群。我们可以进一步扩展这个定义,但许多专家一致认为,在你将集群标记为生产就绪之前,有一组最基本的要求需要你满足。

我们根据典型的 Kubernetes 生产层(如下图所示)收集并分类了这些准备要求。我们理解,每个组织的生产使用场景仍然不同,产品增长和业务目标深刻影响着这些使用场景,从而影响生产准备要求。然而,我们可以合理地认为以下生产就绪检查清单是大多数主流用例的必备清单:

图 1.2 – Kubernetes 基础设施层

图 1.2 – Kubernetes 基础设施层

该图描述了 Kubernetes 基础设施的典型层次。共有六个层次,包括物理、本地或云基础设施;基础设施服务层;集群层;集群服务层;应用支持服务层;最后是应用层。你将在本书中深入了解这些层次,并了解如何设计一个能够无缝集成这些层次的 Kubernetes 生产架构。

生产就绪检查清单

我们已经将生产就绪检查清单项进行了分类,并将它们映射到相应的基础设施层次。每个检查清单项代表了一个设计和实施的关注点,你需要完成这些才能认为你的集群已经准备好投入生产。在本书中,我们将涵盖检查清单项及其设计和实施的详细信息。

集群基础设施

以下检查清单项涵盖了集群层面的生产就绪要求:

  • 在两个独立的节点组上运行etcd。这样做通常是为了简化etcd的操作,如升级和备份,并减少控制平面故障的影响范围。

    此外,对于大型 Kubernetes 集群,这可以通过在特定节点类型上运行etcd,从而为其分配适当的资源,以满足其大量 I/O 需求。

    最后,避免将 Pods 部署到控制平面节点。

  • 运行高可用的工作节点组:您可以通过运行一个或多个具有三个或更多实例的工作节点组来实现。如果您使用公共云提供商来运行这些工作节点组,您应该将它们部署在自动伸缩组内,并分布在不同的可用区。

    实现工作节点高可用性的另一个基本要求是部署 Kubernetes 集群自动扩展器,它允许工作节点根据集群的使用情况进行水平扩展和缩减。

  • 使用共享存储管理解决方案:您应该考虑使用共享存储管理解决方案来持久化和管理有状态应用程序的数据。有很多选择,无论是开源还是商业的,例如 AWS 弹性块存储EBS)、弹性文件系统EFS)、Google Persistent Disk、Azure 磁盘存储、ROOK、Ceph 和 Portworx。它们之间没有绝对对错的选择,但取决于您的应用场景和需求。

  • 部署基础设施可观测性栈:收集关于节点、网络、存储和其他基础设施组件的日志和指标对于监控集群的基础设施至关重要,同时也有助于获得集群性能、利用率和故障排除的有用信息。

    您应该部署一个监控和警报栈,如 Node Exporter、Prometheus 和 Grafana,并部署一个集中式日志栈,如 ELK(Elasticsearch、Logstash 和 Kibana)。另外,您还可以考虑使用完整的商业解决方案,如 Datadog、New Relic、AppDynamics 等。

完成上述要求将确保集群基础设施的生产就绪性。稍后本书中,我们将更详细地展示如何通过基础设施设计、Kubernetes 配置调优和第三方工具使用来实现这些要求。

集群服务

以下检查清单项涵盖了集群服务级别的生产就绪要求:

  • 控制集群访问:Kubernetes 引入了认证和授权选项,并允许集群管理员根据需要配置它们。作为最佳实践,您应该确保身份验证和授权配置已调优并到位。集成外部身份验证提供者来验证集群用户,如 LDAP、OpenID ConnectOIDC)和 AWS IAM。

    对于授权,您需要配置集群以启用 基于角色的访问控制RBAC)、基于属性的访问控制ABAC)和 Webhooks。

  • kube-system 命名空间。对于托管应用程序 Pods 的所有其他命名空间,我们建议分配一个限制性的默认 PSP。

  • 强制执行自定义策略和规则:规则和策略的强制执行对每个 Kubernetes 集群都至关重要。这适用于小型单租户集群和大型多租户集群。Kubernetes 引入了本地对象来实现这一目的,如 Pod 安全策略、网络策略、资源限制和配额。

    对于自定义规则的强制执行,您可以部署一个开放政策代理,如 OPA Gatekeeper。这将使您能够强制执行规则,如 pod 必须设置资源限制、命名空间必须具有特定标签、镜像必须来自已知的仓库等,其他规则也可以执行。

  • 部署并微调集群 DNS:运行 Kubernetes 集群的 DNS 对于名称解析和服务连接至关重要。托管 Kubernetes 已经预部署了集群 DNS,例如 CoreDNS。对于自我管理的集群,您也应该考虑部署 CoreDNS。作为最佳实践,您应该微调 CoreDNS 以减少错误和故障率,优化性能,调整缓存和解析时间。

  • 部署并限制网络策略:Kubernetes 允许单个集群内的所有 Pod 之间进行流量交换。这种行为在多租户集群中是不安全的。作为最佳实践,您需要在集群中启用网络策略,创建一个默认的拒绝所有流量的策略来阻止 Pod 之间的所有流量,然后根据需要创建具有较少限制的 ingress/egress 规则的网络策略,以允许特定 Pod 之间的流量。

  • kube-scan 用于安全配置扫描,kube-bench 用于安全基准测试,Sonobuoy 用于对集群运行 Kubernetes 标准符合性测试。

  • etcd 数据库。

  • control-planekubelet、容器运行时等。您应该部署一个监控和告警栈,如 Node Exporter、Prometheus 和 Grafana,并部署一个中央日志栈,如 EFK(Elasticsearch、Fluentd 和 Kibana)。

完成以上要求将确保集群服务的生产就绪性。在本书后面,我们将更详细地介绍如何通过 Kubernetes 配置调整和第三方工具的使用来实现这些要求。

应用和部署

以下清单项涵盖了应用和部署级别的生产就绪要求:

  • 自动化镜像质量和漏洞扫描:运行低质量应用或使用不良规格编写的应用镜像可能会影响集群的可靠性以及在集群中运行的其他应用。安全漏洞的镜像也是如此。为此,您应运行一个流水线,扫描部署到集群中的镜像,查找安全漏洞和偏离质量标准的情况。

  • 部署 Ingress 控制器:默认情况下,你可以使用负载均衡器和节点端口将 Kubernetes 服务暴露到集群外部。然而,大多数应用有更复杂的路由需求,部署像 Nginx 的 Ingress 控制器这样的解决方案是你应该在集群中使用的事实标准。

  • Secrets 对象有助于在集群内创建和管理秘密及证书。除此之外,你还可以通过部署其他第三方服务来扩展 Secrets 对象,例如使用 Sealed Secrets 进行加密秘密管理,使用 Cert-Manager 自动管理来自证书提供商(如 Let's Encrypt 或 Vault)的证书。

  • 部署应用可观察性栈:你应该利用 Kubernetes 内置的监控功能,例如为 Pod 定义就绪和存活探针。除此之外,还应部署一个中央日志栈来监控应用的 Pod。部署一个黑盒监控解决方案,或使用托管服务来监控应用的端点。最后,考虑使用应用性能监控解决方案,如 New Relic APM、Datadog APM、AppDynamics APM 等。

满足上述要求将确保应用和部署具备生产就绪性。本书后续将详细介绍如何通过 Kubernetes 配置调整和第三方工具的使用来实现这些要求。

Kubernetes 基础设施最佳实践

我们已经了解了 Kubernetes 基础设施的基本知识,并对 Kubernetes 集群的生产就绪性特征有了初步的理解。现在,你已经准备好深入学习基础设施最佳实践和设计原则,这些将引导你建立和运营生产集群的过程。

基础设施设计和管理的 12 项原则

构建一个具有弹性和可靠性的 Kubernetes 基础设施不仅仅是使用配置工具让你的集群运行起来。稳固的基础设施设计是一系列架构决策及其实施的结果。幸运的是,许多组织和专家已经将这些原则和架构决策付诸实际测试。

以下列表总结了可能引导决策者进行 Kubernetes 基础设施设计的核心原则,在本书中,你将详细学习这些原则,并在实践中应用它们:

  1. Go 管理服务:尽管托管服务看起来比自托管服务更贵,但它仍然比自托管服务更受青睐。在几乎所有场景中,托管服务比自托管服务更高效且更可靠。我们将这一原则应用于 Kubernetes 托管服务,如 Google Kubernetes EngineGKE)、Azure Kubernetes ServiceAKS)和 Elastic Kubernetes ServiceEKS)。这一原则不仅适用于 Kubernetes,还适用于每一项基础设施服务,如数据库、对象存储、缓存以及许多其他服务。有时,托管服务可能比自托管服务可定制性差或更昂贵,但在其他所有情况下,您应该始终优先考虑托管服务。

  2. 简化:Kubernetes 不是一个简单的平台,无论是设置还是操作。它解决了在一个应用程序可以扩展到为数百万用户提供服务的互联网规模工作负载管理的复杂性,在这个世界中,云原生和微服务架构是大多数现代应用程序的首选方法。

    对于基础设施的创建和操作,我们不需要增加额外的复杂性,因为基础设施本身旨在对产品透明并无缝对接。组织的主要关注点和焦点应始终是产品,而非基础设施。

    这就是简化原则的作用,它并不意味着应用琐碎的解决方案,而是简化复杂的解决方案。这使我们做出像选择更少的 Kubernetes 集群进行操作,或者避免多云等决策;只要我们没有一个坚实的用例来证明其必要性。

    简化原则适用于我们部署到集群的基础设施功能和服务,因为将每个服务都加到集群中可能看起来很有吸引力,我们会认为这样可以构建一个功能强大且富有特性的集群。相反,这样做最终会使操作复杂化,并降低平台的可靠性。此外,我们也可以将同样的原则应用于我们选择的技术栈和工具,因为统一团队之间的工具和技术栈被证明比使用一套不一致的工具更高效,后者最终难以管理,即使这些工具中的某一个对特定用例来说是最佳选择,简化总是会带来回报。

  3. 一切皆代码(XaC):这是现代基础设施和 DevOps 团队的默认实践。推荐的做法是使用声明式的 基础设施即代码IaC)和 配置即代码CaC)工具与技术,优于它们的命令式对等工具。

  4. 不可变基础设施:不可变性是一种基础设施配置概念和原则,在每次部署时,我们会替换系统组件,而不是在原地更新它们。我们始终通过镜像或声明式代码创建不可变组件,在这些组件上我们可以构建、测试并验证这些不可变系统,每次都能得到相同的可预测结果。Docker 镜像和 AWS EC2 AMI 就是这一概念的例子。

    这个重要原则使我们能够实现 Kubernetes 集群的一个目标特性,即将集群视为牲畜而非宠物。

  5. 自动化:我们生活在一个软件自动化的时代,我们倾向于将一切自动化;这样更高效,管理和扩展也更容易,但我们需要将 Kubernetes 中的自动化提升到更高的层次。Kubernetes 用于自动化容器生命周期管理,它还带来了先进的自动化概念,如操作员(Operators)和 GitOps,它们高效且能真正实现自动化的自动化。

  6. 标准化:拥有一套标准有助于减少团队在对齐和协作中的困难,简化流程的扩展,提升整体质量,并增加生产力。这对计划在生产环境中使用 Kubernetes 的公司和团队来说至关重要,因为这涉及到与不同基础设施部分的集成、将服务从本地迁移到云端,以及更多的复杂性。

    定义一套标准涵盖了操作手册和执行手册的流程,以及技术标准化,例如在团队中使用 Docker、Kubernetes 和标准工具。这些工具应具备特定的特点:开源但经过生产环境验证,支持其他原则,如基础设施即代码、不变性、云中立,并且易于使用和部署,且基础设施要求最低。

  7. 真实数据来源:拥有单一的数据源是现代基础设施管理和配置的基石和推动力。源代码控制系统,如 Git,是存储和版本控制基础设施代码的标准选择,其中拥有一个专门用于基础设施的源代码仓库是一种推荐的做法。

  8. 设计可用性:Kubernetes 是基础设施和应用层高可用性的关键推动力。自第一天起就将高可用性作为设计支柱,对充分发挥 Kubernetes 的全部功能至关重要。因此,在每个设计层面,您都应考虑高可用性,从云层和基础设施即服务IaaS)层开始,通过选择多区域或多可用区架构,然后在 Kubernetes 层设计多主集群,最后在应用层部署每个服务的多个副本。

  9. 云中立:云中立意味着你可以在任何云平台上运行工作负载,且尽量减少对特定供应商的依赖,但要注意不要过于痴迷于这个理念,也不要将其作为唯一目标。Docker 和 Kubernetes 是社区应对创建和管理云中立平台的解决方案。这一原则也延伸到其他技术和工具的选择(例如 Terraform 与 CloudFormation 的对比)。

  10. 业务持续性:公有云通过其弹性解决了一个长期困扰在线服务业务连续性的问题,特别是在几乎实现即时扩展基础设施的情况下,使得小型企业也能享受到以前只有大型科技公司才能拥有的基础设施奢侈。

    然而,应对日益增长的扩展需求并实现实时性仍然是一个挑战,随着容器的引入,部署和运行工作负载应用变得可以在几秒钟内轻松部署和扩展。这将压力重新转移到了 Kubernetes 和底层基础设施层面,要求它们支持容器的实时扩展能力。你需要为未来做出扩展决策,以支持业务扩展和持续性。诸如是否使用单一大型集群还是多个较小集群,如何管理基础设施成本,节点的合适大小是什么,以及高效的资源利用策略是什么等问题……所有这些问题都需要具体的答案和重要的决策!

  11. 为故障做好计划:许多分布式系统特性适用于 Kubernetes 容器化应用;特别是容错性,我们预期会发生故障,并为系统组件故障做出规划。在设计 Kubernetes 集群时,你必须使用高可用性原则来设计它,以便在发生中断和故障时依然能生存下来。但你还必须有意识地为故障做计划。你可以通过应用混沌工程思想、灾难恢复解决方案、基础设施测试和基础设施 CI/CD 来实现这一目标。

  12. 操作效率:公司通常低估了在生产环境中操作容器所需的努力——如何应对第二天及以后的问题,以及如何为故障、集群升级、备份、性能调优、资源利用和成本控制做好准备。在这个阶段,公司需要弄清楚如何持续交付更改到不断增加的生产和非生产环境中,如果没有适当的操作实践,这可能会造成瓶颈并减缓业务增长,更严重的是,导致系统不可靠,无法满足客户的期望。我们见证了成功的 Kubernetes 生产环境部署,但最终,由于运维团队和薄弱的实践,事情还是崩溃了。

这 12 条原则已被证明是成功的大规模云基础设施部署的常见模式。我们将在本书的大部分章节中应用这些原则,并在做出相关技术决策时,尽量突出每一条原则。

应用定义和部署

可能,一个成功且高效的 Kubernetes 集群无法挽救应用程序糟糕的设计和实现。通常,当应用程序未遵循容器化最佳实践和高可用设计时,它将失去由底层 Kubernetes 提供的云原生优势:

  • 容器化:这是云工作负载的事实标准交付和部署形式。为了确保生产环境的可靠性,容器化的最佳实践起着至关重要的作用。你将在接下来的章节中详细了解这一原则。不良的实践可能导致生产环境的不稳定和灾难性的停机,例如忽视容器的优雅关机和进程终止信号,以及不恰当的应用程序重试连接到依赖服务。

  • 应用程序高可用性:通过部署两个或更多应用副本,并利用 Kubernetes 的高级调度技术(节点选择器、污点、亲和性和标签)将副本部署到不同的节点和可用区,同时定义 Pod 中断策略来实现这一目标。

  • 应用程序监控:通过定义就绪和存活探针,并进行不同检查,部署 应用程序性能监控APM),以及使用著名的监控方法,如 RED(请求率、错误率和持续时间)和 USE(利用率、饱和度和错误),来实现这一目标。

  • 部署策略:Kubernetes 和云原生使部署变得比以往更简单。这些频繁的部署为企业带来了好处,比如缩短上市时间、更快的客户反馈以及整体提高产品质量。然而,这也有一些负面影响,因为如果没有正确规划和管理,频繁的部署可能会影响产品的可靠性和正常运行时间。在这种情况下,定义部署和回滚策略(滚动更新、重建、金丝雀、蓝绿部署和常规部署)成为应用部署的最佳实践之一。

这四个领域的考虑将确保应用程序顺利部署到 Kubernetes 集群中并顺利运行,尽管在这些领域下,基于你组织的偏好和 Kubernetes 使用案例,仍然需要做出更为详细的技术决策。

流程、团队与文化

云转型带来了对组织文化和流程的巨大变化,以及它们管理和运维基础设施和应用程序的方式。DevOps 反映了这种深刻影响,它体现了将云思维融入组织文化中的影响,改变了公司如何进行开发和运维,以及其内部团队的组织方式。

一天又一天,开发与运维之间的界限越来越模糊,通过引入 Kubernetes 和云原生方法,DevOps 团队正朝着站点可靠性工程SRE)模型转型,并且还在招聘专门的平台团队,因为这两种方法都考虑了管理和操作 Kubernetes 的团队结构推荐实践。

云原生方法

云原生计算基金会CNCF)将云原生定义为在现代动态环境中运行的可扩展应用,使用容器、微服务和声明式 API 等技术。Kubernetes 是 CNCF 的第一个项目,也是世界上最受欢迎的容器编排平台。

云原生计算使用开源的现代商业第三方软件堆栈来构建、打包和部署微服务形式的应用程序。容器和容器编排工具如 Kubernetes 是云原生方法中的关键元素,它们使得实现云原生状态并满足 12 因素应用方法论的要求成为可能。这些技术提升了资源利用、分布式系统的可靠性、扩展性和可观察性等方面。

12 因素应用方法论

12 因素应用方法论定义了开发者和 DevOps 工程师在构建和运营软件即服务时需要遵循的特性和设计方面。它与云原生架构和方法紧密相关。了解更多内容,请访问:12factor.net/

云原生计算基金会

2014 年,谷歌开源了 Kubernetes,它与谷歌内部的容器调度器 Borg 非常相似。谷歌多年来一直在其数据中心使用 Borg 来调度容器和工作负载。后来,谷歌与 Linux 基金会合作创建了 CNCF,并将 Borg 的实现重写为 Go 语言,改名为 Kubernetes。之后,许多科技公司加入了 CNCF,包括谷歌的云竞争对手:微软和亚马逊。

CNCF 的目标是为现代应用开发构建和管理平台及解决方案。它监督和协调支持云原生软件开发的开源技术和项目,但也包括一些商业提供商的关键项目。

我们为什么应该关注云原生

CNCF 声明如下:

“公司意识到,他们需要成为一家软件公司,即使他们不在软件行业。例如,Airbnb 正在彻底改变酒店业,而更多传统酒店则在竞争中挣扎。云原生使得 IT 和软件可以更快发展。采用云原生技术和实践使公司能够自行开发软件,促使业务人员与 IT 人员紧密合作,赶上竞争对手,并为客户提供更好的服务。CNCF 技术使得云的可移植性成为可能,而不受供应商锁定的限制。”

CNCF 的云原生建议和软件堆栈是高质量、最新 Kubernetes 基础设施的基石,这是我们打算交付和运营的生产级基础设施的关键部分。遵循 CNCF 并跟踪其解决方案生态是 Kubernetes 平台创建者和用户应该将其列为清单首位的最佳实践之一。

云原生生态系统与景观

云原生景观是一个结合了开源和商业软件项目的生态系统,由 CNCF 及其成员监督和支持。CNCF 根据云原生功能和基础设施层次对这些项目进行了分类。基本上,云原生景观分为四个层次:

  • 提供:这一层包括基础设施自动化和配置管理的项目,如 Ansible 和 Terraform,还有容器注册表,如 Quay 和 Harbor,然后是安全性和设备,如 Falco、TUF 和 Aqua,最后是密钥管理,如 Vault。

  • 运行时:这一层包括容器运行时的项目,如 containerd 和 CRI-O,云原生存储,如 Rook 和 Ceph,最后是云原生网络插件,如 CNI、Calico 和 Cilium。

  • 编排与管理:这是 Kubernetes 作为调度器和编排工具的领域,还有其他关键项目,如 CoreDNS、Istio、Envoy、gRPC 和 KrakenD。

  • 应用定义与开发:这一层主要涉及应用程序及其生命周期,涵盖了 CI/CD 工具,如 Jenkins 和 Spinnaker,构建与应用定义,如 Helm 和 Packer,最后是分布式数据库、流处理和消息传递。

CNCF 生态系统提供了涵盖云原生和 Kubernetes 需求的各个方面的建议。每当适用时,我们将利用这些 CNCF 项目来满足集群需求。

云原生路径图

云原生路径图是 CNCF 推荐的穿越云原生景观的路径。虽然这个路线图是为云原生转型而设计的,但它仍然与我们的 Kubernetes 生产路径相交,因为将 Kubernetes 部署为编排管理工具是这个路径图中的一个重要里程碑。

我们不得不承认,大多数 Kubernetes 用户正在启动他们的云转型之旅或正在其中,因此理解这个路径图是规划和实施成功的 Kubernetes 部署的基石。

CNCF 推荐以下阶段用于任何云原生转型,这些阶段也得到了云原生生态系统中不同项目的支持:

  1. 容器化:容器是云原生应用程序的打包标准,这是你进行应用程序云迁移的第一阶段。Docker 容器被证明高效、轻量且便于移植。

  2. 持续集成与持续交付/部署(CI/CD):CI/CD 是在将应用程序容器化后自然而然的第二步,你需要自动化构建容器镜像,每当代码发生更改时,这将简化测试和应用程序在不同环境中的交付,包括开发、测试、阶段环境,甚至进一步到生产环境。

  3. 编排与应用定义:一旦你部署了应用程序的容器并自动化了这一过程,你将面临容器生命周期管理的挑战,并且最终会创建大量的自动化脚本来处理容器的重启、扩展、日志管理、健康检查和调度。这时,编排工具就登场了,它们提供开箱即用的管理服务,使用像 Kubernetes 这样的编排工具,你不仅能够进行更多的容器生命周期管理,还能得到管理云底层基础设施的层次,并在其上构建云原生和微服务的基础。

  4. 可观察性与分析:监控和日志记录是云原生应用程序的组成部分;这些信息和指标能够帮助你高效地操作系统,获得可行性,并维护健康的应用程序和 服务级目标SLOs)。

  5. 服务代理、发现与服务网格:在这一阶段,你的云原生应用和服务变得越来越复杂,你将寻求提供服务发现、DNS、高级负载均衡和路由、A/B 测试、金丝雀测试和部署、速率限制以及访问控制等服务。

  6. 网络与策略:Kubernetes 和分布式容器的网络模型为你的基础设施带来了复杂性,这就产生了对标准化且灵活的网络标准的迫切需求,比如 CNCF CNI。因此,你需要部署符合要求的插件,如 Calico、Cilium 或 Weave,以支持网络策略、数据过滤和其他网络需求。

  7. 分布式数据库与存储:云原生应用模型强调可扩展性,而传统的数据库无法满足云原生扩展需求的速度。这正是 CNCF 分布式数据库填补空白的地方。

  8. 流式处理与消息传递:CNCF 提议使用 gRPC 或 NATS,它们提供比 JSON-REST 更高的性能。gRPC 是一个高性能的开源 RPC 框架。NATS 是一个简单且安全的消息传递系统,可以在任何地方运行,从大型服务器和云实例到边缘网关和物联网设备。

  9. 容器注册表和运行时:容器注册表是存储和管理容器镜像的集中位置。选择合适的注册表,具备包括性能、漏洞分析和访问控制等功能,是云原生之旅中的一个重要阶段。运行时是负责运行容器的软件层。通常,在容器化阶段开始时,你会使用 Docker 运行时,但最终你可能会考虑使用 CNCF 支持的运行时,如 CRI-O 或 containerd。

  10. 软件分发更新框架TUF)及其 Notary 实现都是由 CNCF 资助的项目,它们提供现代的云原生软件分发解决方案。

将前面的云原生转型阶段视为推荐路径是明智的做法。公司不太可能严格按照这张路线图执行,但它为启动你的云转型之旅提供了一个很好的基础。

总结

构建生产级、可靠的 Kubernetes 基础设施和集群不仅仅是配置集群并将应用程序部署到其中。这是一个持续的过程,结合了基础设施和服务的规划、设计、实施、CI/CD、运营和维护。

每个方面都有自己的一套技术决策需要做出,遵循的最佳实践,以及需要克服的挑战。

到现在为止,你已经对 Kubernetes 基础设施的基本知识、生产挑战和就绪功能有了简要了解。最后,我们探讨了构建和管理成功的 Kubernetes 生产环境的行业最佳实践,并了解了云原生方法。

在下一章中,我们将学习如何设计和架构一个成功的 Kubernetes 集群及相关基础设施的实际细节,同时探讨在部署生产集群时需要处理的技术和架构决策、选择和替代方案。

进一步阅读

如果你不熟悉 Kubernetes 的基本概念,可以参考以下书籍:

Kubernetes 入门 – 第三版www.packtpub.com/virtualization-and-cloud/getting-started-kubernetes-third-edition

第二章:第二章:构建生产级 Kubernetes 基础设施架构

在上一章中,您了解了 Kubernetes 的核心组件和基础设施基础知识,以及为何将 Kubernetes 部署到生产环境是一个充满挑战的过程。我们介绍了 Kubernetes 集群的生产就绪特性,并提供了我们的推荐检查清单,帮助确保您的集群满足生产就绪的要求。

我们还介绍了一组通过构建生产级云环境所学到的基础设施设计原则。在本书中,当我们做出架构和设计决策时,我们将这些原则作为指导,并强烈建议云基础设施团队在为 Kubernetes 和云平台架构新基础设施时考虑这些原则。

在本章中,您将了解在设计 Kubernetes 基础设施时需要解决的重要架构决策。我们将探讨每个决策的替代方案和选择,以及可能的优缺点。此外,您还将学习有关云架构的考虑因素,如扩展性、可用性、安全性和成本。我们的目的是提供指导而非做出最终决策,因为每个组织的需求和使用场景不同。我们的角色是探索这些选项,并引导您进行决策。在可能的情况下,我们会阐明我们在本书中的偏好选择,并在实际练习中遵循这些选择。

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

  • 理解 Kubernetes 基础设施设计考虑因素

  • 探索 Kubernetes 部署策略的替代方案

  • 设计 Amazon EKS 基础设施

理解 Kubernetes 基础设施设计考虑因素

在 Kubernetes 基础设施设计方面,有一些虽然重要但需要考虑的因素。几乎每种云基础设施架构都包含相同的一组考虑因素;然而,我们将从 Kubernetes 视角讨论这些考虑因素,并对其进行阐释。

扩展性和弹性

公有云基础设施,如 AWS、Azure 和 GCP,引入了前所未有的扩展性和弹性功能。Kubernetes 和容器化技术应运而生,基于这些功能并进一步扩展。

在设计 Kubernetes 集群基础设施时,您应该确保架构涵盖以下两个方面:

  • 可扩展的 Kubernetes 基础设施

  • 部署到 Kubernetes 集群的可扩展工作负载

为了实现第一个要求,有些部分依赖于底层基础设施,无论是公有云还是本地部署,其他部分则依赖于 Kubernetes 集群本身。

当你选择使用托管 Kubernetes 服务(如 EKS、AKS 或 GKE)时,通常可以解决第一部分的问题,因为集群的控制平面和工作节点将具有可扩展性,并受到其他可扩展基础设施层的支持。

然而,在某些使用场景中,你可能需要部署一个自管理的 Kubernetes 集群,无论是在本地还是在云端。在这种情况下,你需要考虑如何支持扩展性和弹性,以使你的 Kubernetes 集群能够充分发挥其容量。

在所有公共云基础设施中,都有计算自动扩展组的概念,Kubernetes 集群就是建立在这些基础之上的。然而,由于 Kubernetes 上运行的工作负载的特点,扩展需求应与集群调度操作同步。这就是 Kubernetes 集群自动扩展器为我们提供帮助的地方。

集群自动扩展器CAS)是一个 Kubernetes 集群附加组件,你可以选择将其部署到集群中,它会根据你在 CAS 中指定的条件和配置自动扩展或缩减工作节点的大小。基本上,当由于计算资源不足而导致 Pod 无法调度时,它会触发集群的扩展;而当存在未充分利用的节点时,它会触发集群缩减,并将这些节点的 Pod 重新调度到其他节点上。你需要考虑云提供商启动新节点所需的时间,因为这可能对时间敏感的应用程序构成问题,在这种情况下,你可以考虑配置 CAS 以启用节点的过度预配。

如需了解更多有关 CAS 的信息,请参阅以下链接:github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler

为了实现第二个扩展需求,Kubernetes 提供了两种解决方案来实现 Pod 的自动扩展:

  • 水平 Pod 自动扩展器 (HPA):它的工作原理类似于云端自动扩展组,但作用在 Pod 部署层面。可以将 Pod 看作虚拟机实例。HPA 根据特定的度量阈值扩展 Pod 的数量。这些度量值可以是 CPU 或内存利用率,或者你也可以定义自定义度量。要了解 HPA 的工作原理,你可以继续阅读相关内容:kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/

  • 垂直 Pod 自动缩放器(VPA):它通过根据 Pod 的使用指标增加 CPU 和内存限制,来垂直扩展 Pod。可以把 VPA 想象成通过更改公有云中虚拟机实例的类型来进行扩容/缩容。VPA 可能会影响 CAS 并触发扩容事件,因此你应该修订 CAS 和 VPA 配置,以使它们保持一致,避免任何不可预见的扩展行为。要了解 VPA 的工作原理,你可以继续阅读这里的内容:github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler

我们强烈建议在生产部署中使用 HPA 和 VPA(在非生产环境中则不必要)。我们将在第八章部署无缝且可靠的应用程序中,给出如何使用它们部署生产级应用程序和服务的示例。

高可用性和可靠性

Uptime 意味着可靠性,通常是基础设施团队衡量和提升的主要指标。Uptime 驱动服务级目标SLOs)和与客户的服务水平协议SLAs),它还表明你的系统和软件即服务SaaS)产品有多稳定和可靠。高可用性是提升 Uptime 的关键,当涉及到 Kubernetes 集群的基础设施时,同样的规则依然适用。这就是为什么设计一个高度可用的集群和工作负载是生产级 Kubernetes 集群的基本要求。

你可以在不同的可用性级别上架构一个高度可用的 Kubernetes 基础设施,具体如下:

  • 单一区域的公有云集群(单一数据中心):这被认为是所有架构中最简单的,但也是风险最高的。我们不推荐这种解决方案。

  • 多区域(多个数据中心)但在同一云区域中的集群:这仍然容易实现,提供了更高的可用性,并且是 Kubernetes 集群的常见架构。然而,当你的云服务提供商的某个区域发生完全故障时,整个集群将完全不可用。虽然这种区域故障很少发生,但你仍然需要为这种情况做好准备。

  • 跨多区域集群,但在同一云服务提供商内:在这种架构中,通常会运行多个联合的 Kubernetes 集群来承载生产工作负载。这通常是高可用性的首选解决方案,但它需要付出一定的代价,使其实现和运维变得困难,特别是可能存在的网络性能差,以及有状态应用的共享存储。我们不推荐这种架构,因为对于大多数 SaaS 产品来说,在单一区域和多个可用区部署 Kubernetes 已经足够。然而,如果出于除高可用性之外的其他原因,您有多区域的需求,您可以考虑将多区域 Kubernetes 联合集群作为解决方案。

  • 跨多云部署的多个集群:由于云服务提供商之间的兼容性限制、集群间网络复杂性、跨云提供商的网络流量成本增加以及实施和运维的挑战,这种架构仍然不太流行。然而,值得一提的是,越来越多的多云管理解决方案正致力于解决这些挑战,您可能会考虑使用 Google 的 Anthos 等多集群管理解决方案。您可以在这里了解更多信息:cloud.google.com/anthos

如您所见,Kubernetes 在高可用性设置方面有不同的架构选择,我可以说,拥有不同的选择使得 Kubernetes 在不同的用例中更具强大功能。虽然第二种选择目前是最常见的,因为它在实现和运维的简便性与高可用性水平之间取得了平衡。我们乐观地期待着一个时刻的到来,那时我们可以轻松地跨云提供商部署 Kubernetes 集群,并在不增加运维负担和成本的情况下,获得所有的高可用性优势。

至于集群的可用性本身,我相信不言而喻,Kubernetes 组件应该以高度可用的模式运行,即控制平面有三台或更多节点,或者最好是让云平台为您管理控制平面,如 EKS、AKE 或 GKE。至于工作节点,您需要运行一个或多个自动扩展组或节点组/池,这样可以确保高可用性。

另一个需要考虑的领域是您将部署到集群中的 Pods 和工作负载的高可用性。虽然这超出了本书的范围,但仍然值得一提的是,开发新应用和服务,或现代化现有应用使其能够以高可用性模式运行,是利用强大的 Kubernetes 基础设施所提供的众多功能的唯一方式。否则,您将拥有一个非常强大的集群,但只能运行单实例的单体应用!

安全性和合规性

Kubernetes 的基础设施安全性贯穿于集群的所有层面,从网络层开始,到操作系统层,再到集群服务和工作负载。幸运的是,Kubernetes 在安全性、加密、身份验证和授权方面提供了强有力的支持。我们将在本书的第六章《有效地保障 Kubernetes 安全性》中学习安全相关内容。然而,在设计集群基础设施时,您应关注与安全相关的重要决策,例如保护 Kubernetes API 服务器端点、集群网络设计、安全组、防火墙、控制平面组件、工作节点和公共互联网之间的网络策略等。

您还需要提前规划基础设施组件或集群与身份管理提供商之间的集成。这通常取决于贵组织的安全政策,您需要与 IT 和安全团队进行对接。

另一个需要考虑的方面是集群的审计和合规性。大多数组织都有云治理政策和合规要求,您需要在继续部署生产环境到 Kubernetes 之前了解这些要求。

如果决定使用多租户集群,安全要求可能会更加复杂,明确集群租户之间以及来自不同内部团队的集群用户之间的边界,可能会导致诸如部署服务网格、强化集群网络策略、以及实施更严格的基于角色的访问控制RBAC)机制等决策。这一切都会影响您在架构第一个生产集群基础设施时的决策。

Kubernetes 社区非常重视合规性和质量,因此有多种工具和测试可以确保您的集群达到可接受的安全性和合规性水平。在第六章《有效地保障 Kubernetes 安全性》中,我们将学习这些工具和测试。

成本管理和优化

云成本管理是所有采用云技术的组织面临的重要因素,无论是刚开始使用云的组织,还是已经在云中的组织。将 Kubernetes 加入云基础设施预计将带来成本节省,因为容器化可以使你高效利用计算资源,规模上是虚拟机无法比拟的。一些组织在迁移到容器和 Kubernetes 后实现了高达 90% 的成本节省。

然而,如果没有适当的成本控制,成本可能会再次上升,最终导致许多基础设施成本浪费,并且 Kubernetes 集群无法控制。有许多工具和最佳实践可以考虑用于成本管理,但我们主要希望专注于在基础设施设计过程中需要考虑的行动和技术决策。

我们认为有两个重要方面需要决策,而这些决策将无疑影响你的集群基础设施架构:

  • 运行一个单一的多租户集群与多个集群(即,每个租户一个集群)之间的对比

  • 集群容量:是运行少量的大型工作节点,还是运行大量的小型工作节点,或是两者的混合?

没有明确正确的决策,但我们将在下一节探讨这些选择,以及如何做出决策。

以下是关于成本优化的其他考虑因素,在此可以做出提前决策:

  • 使用临时/可抢占实例:这一做法已被证明能够实现巨大的成本节省;然而,它也有代价!任何时候都有失去工作负载的风险,这会影响产品的正常运行时间和可靠性。有一些选项可以克服这一问题,例如将临时实例用于非生产工作负载,如开发环境或 CI/CD 流水线,或任何可以容忍中断的生产工作负载,例如数据批处理。

    我们强烈建议使用临时实例作为工作节点,你可以将它们运行在各自的节点组/池中,并将它们分配给那些不太关注会被中断的工作负载。

  • Kubernetes 成本可观察性:大多数云平台为所有云资源提供成本可视化和分析。然而,在集群的部署/服务层级上具有成本可视化至关重要,这需要提前规划,因此你需要使用隔离的工作负载、团队、用户、环境,并且使用命名空间并为其分配资源配额。通过这样做,你将确保使用成本报告工具时,能够生成与服务或集群操作相关的使用情况报告。这对于进一步的成本削减决策非常重要。

  • Kubernetes 集群管理:当你运行一个单租户集群,或者为每个开发环境创建一个集群时,你通常会在账户中拥有大量的集群,这可能会导致云成本的增加。解决这一问题的方法是从第一天起就设置集群管理解决方案。这个解决方案可以像一个简单的集群自动缩放脚本,在空闲期间减少工作节点,或者可以是一个完整的自动化系统,配有仪表盘和主集群来管理其余的集群。

第九章监控、日志记录与可观测性》和第十章高效 Kubernetes 集群的操作与维护》中,我们将学习成本可观测性和集群操作。

可管理性与操作效率

通常,当一个组织开始建立 Kubernetes 基础设施时,他们将大部分时间、精力和关注投入到基础设施设计和部署的紧急和关键需求中,我们通常称之为 Day 0 和 Day 1。一个组织很难把精力投入到未来可能遇到的操作和可管理性问题上(Day 2)。

这一点可以通过缺乏 Kubernetes 经验以及操作挑战的种类来解释,或者是被驱动于获得 Kubernetes 带来的好处,尤其是与开发相关的,如提高开发人员的生产力与敏捷性,自动化发布和部署。

所有这些都导致组织和团队对 Day 2 的准备不足。在本书中,我们尝试在设计、实现和操作之间保持平衡,并阐明操作中的重要方面,学习如何从 Day 0 开始进行规划,特别是与可靠性、可用性、安全性和可观测性相关的内容。

Kubernetes 的操作挑战

这些是大多数团队在将 Kubernetes 部署到生产环境后所面临的常见操作和可管理性挑战。在这一阶段,你需要重新思考并提前考虑解决方案,以便妥善应对这些挑战:

  • 可靠性与扩展性:当你的基础设施进行扩展时,你可能会拥有数十个或数百个集群,或拥有成百上千个节点的集群,以及不同环境类型的大量配置。这使得管理应用程序的 SLA/SLO 变得更加困难,甚至维护正常运行时间目标以及诊断集群问题也可能非常具有挑战性。团队需要不断提升 Kubernetes 知识和故障排除技能。

  • 可观察性:毫无疑问,Kubernetes 是复杂的,这使得监控和日志记录成为必备服务,一旦你的集群投入生产,否则你将很难识别问题和故障。在这方面,你需要部署监控和日志记录工具,并定义基本的可观察性指标和阈值。

  • etcdkube-proxy、Docker 镜像以及集群插件的配置在集群生命周期中变得难以管理。这要求从一开始就需要正确的工具。自动化和基础设施即代码(IaC)工具,如 Terraform、Ansible 和 Helm,通常用于帮助解决这一问题。

  • 灾难恢复:当你的集群出现部分或完全故障时,会发生什么?恢复计划是什么?如何减轻这种风险,并缩短恢复集群和工作负载的平均时间?这需要部署正确的工具,并编写备份、恢复和危机管理的操作手册。

  • 安全性与治理:你需要确保在生产集群和工作负载方面,应用并执行安全最佳实践和治理政策。由于 Kubernetes 的复杂性、软隔离技术、灵活性以及它带来的开发和发布生命周期的快速步伐,这变得具有挑战性。

还有其他运营挑战。然而,我们发现,只要我们遵循以下基础设施最佳实践和标准,大多数这些问题都可以得到缓解:

  • 基础设施即代码(IaC):这是现代基础设施和 DevOps 团队的默认实践。使用声明式的 IaC 工具和技术,而非命令式工具,是推荐的方法。

  • 自动化:我们生活在软件自动化的时代,我们倾向于自动化一切;这样做更高效,更容易管理和扩展,但我们需要将 Kubernetes 的自动化提升到另一个层次。Kubernetes 本身就具备自动化容器生命周期的能力,同时也具备高级自动化概念,如操作员(operators)和 GitOps,这些概念高效且能够真正实现自动化的自动化。

  • 标准化:拥有一套标准可以帮助减少团队在对齐和协作方面的困难,简化流程的扩展,提高整体质量,并增加生产力。对于计划在生产中使用 Kubernetes 的公司和团队来说,这尤为重要,因为这涉及到与不同基础设施部分的集成、将服务从本地迁移到云端以及许多进一步的复杂性。

    定义你的标准集包括操作手册和执行手册的流程,以及技术标准化——在团队间使用 Docker、Kubernetes 和标准工具。这些工具应该具备特定特点:开源但在生产环境中经过充分验证,能够支持其他原则,如基础设施即代码(IaC)、不可变性、云无关性,并且能够简单使用和部署,且对基础设施的要求最低。

  • 单一真实来源:拥有一个真实来源是现代基础设施管理和配置的基石和推动力。源代码控制系统,如 Git,已成为存储和版本化基础设施代码的标准选择,在基础设施代码管理中,拥有一个单一且专门的源代码库是推荐的最佳实践。

管理 Kubernetes 基础设施是关于管理复杂性的问题。因此,拥有一个稳固的基础设施设计,应用最佳实践和标准,提升团队的 Kubernetes 专业技能和经验,都会帮助确保操作顺畅和易于管理。

探索 Kubernetes 部署策略的替代方案

Kubernetes 及其生态系统提供了关于部署、编排和操作工作负载的广泛选择。这种灵活性是一个巨大的优势,使得 Kubernetes 能够适应不同的使用场景,从本地和云端的常规应用到物联网(IoT)和边缘计算。然而,选择意味着责任,在本章中,我们将了解在集群部署架构中需要评估并做出的技术决策。

需要考虑和决策的一个重要问题是:你应该在哪里部署集群,以及你可能需要多少个集群来运行容器化工作负载?答案通常由业务和技术因素共同驱动;例如现有基础设施、云转型计划、云预算、团队规模和业务增长目标等。这些因素都会影响决策,这也是为什么 Kubernetes 项目的负责人需要与组织内的团队和高层管理人员合作,达成关于决策驱动因素的共同理解,并商定业务的正确方向。

我们将探索一些常见的 Kubernetes 部署架构替代方案,包括它们的使用场景、优点和缺点:

  • 多可用区集群:这是在公有云中部署高可用性HA)集群的主流架构。因为大多数公有云提供商都支持在多可用区中运行集群,同时,这也能实现一个可接受的高可用性水平。因此,绝大多数新的 Kubernetes 用户选择这种方案。然而,如果你有必须在不同区域运行工作负载的需求,这个选项就不太适用了。

  • 多区域集群:除非你有在多个区域运行集群的需求,否则很少有动机选择这种架构。虽然公共云服务提供商失去整个区域的情况非常罕见,但如果你有足够的预算进行合理的设计并克服操作上的挑战,那么可以选择多区域配置。它肯定会为你提供更高的高可用性和可靠性水平。

  • 混合云集群:混合云是一个常见的做法,适用于那些从本地迁移到公共云的组织,并且正处于过渡期,工作负载或数据在旧基础设施和新云基础设施之间分割。混合云也可以是一个永久性的配置,组织希望将部分基础设施保留在本地,无论是出于安全原因(例如敏感数据)还是因为无法迁移到云端。Kubernetes 是混合云模型的推动者,特别是通过像 Google Anthos 这样的托管集群管理解决方案。不过,这无疑会带来更高的资源配置和运营成本。

  • 多云集群:与混合云集群不同,我认为多云集群是一个不太常见的模式,因为它通常缺乏强有力的推动力。你可以在多云集群中运行多个不同的系统,出于各种原因,但通过 Kubernetes 在两个或更多云中部署单一系统并不常见,在朝这个方向发展之前,你应该谨慎考虑。然而,我能理解一些组织这样做的动因,比如避免与某个特定云提供商的绑定,利用不同提供商的定价模型进行成本优化,减少延迟,甚至实现工作负载的终极可靠性。

  • 本地集群:如果一个组织决定不迁移到云端,Kubernetes 仍然可以在本地管理他们的基础设施。实际上,Kubernetes 是管理本地工作负载的现代化合理选择,然而,目前提供稳固的本地管理 Kubernetes 解决方案的还非常少。

  • 边缘集群:Kubernetes 在边缘计算和物联网领域越来越受到关注。它为底层硬件提供了抽象,非常适合分布式计算需求,而且庞大的 Kubernetes 生态系统帮助推出了多个符合边缘计算特点的开源和第三方项目,如 KubeEdge 和 K3s。

  • 本地集群:你可以使用像 Minikube 或 Kind(Kubernetes in Docker)这样的工具在本地机器上运行 Kubernetes。使用本地集群的目的是进行试验、学习和开发者使用。

我们已经讨论了各种集群部署架构和可用的模型及其应用场景。在接下来的章节中,我们将学习如何设计本书中将使用的 Kubernetes 基础设施,以及围绕它的技术决策。

设计 Amazon EKS 基础设施

在本章中,我们讨论并探讨了 Kubernetes 集群设计的各个方面,以及在设计时需要考虑的不同架构因素。现在,我们需要将这些内容汇总成本书中将要遵循的设计方案。我们在这里做出的决定并不意味着它们是唯一正确的选择,但这是我们在本书的实际练习中为确保最基本可接受的生产集群所采取的首选设计。你当然可以采用相同的设计,但可以进行修改,比如集群大小调整。

在接下来的章节中,我们将探讨有关云服务提供商、配置和配置工具的选择,以及整体基础设施架构。在后续章节中,我们将基于这些选择进行构建,并使用它们来部署类似生产环境的集群,同时在集群上部署配置和服务。

选择基础设施提供商

正如我们在前面的章节中所学,部署 Kubernetes 有多种方式。你可以在本地、内网,或公有云、私有云、混合云、多云环境或边缘位置进行部署。每种基础设施类型都有其使用场景、优点和缺点。然而,最常见的还是公有云,其次是混合云模式。其余选项通常仅适用于特定场景。

在像我们这样的单本书中,我们无法讨论每种基础设施平台,因此我们决定选择一种最常见的 Kubernetes 部署方式,即使用公有云之一(AWS、Azure 或 GCP)。你仍然可以使用其他云服务提供商、私有云,甚至是本地部署,书中讨论的大多数概念和最佳实践依然适用。

在选择公有云时,我们并不推崇某一特定云服务提供商,当然我们推荐使用你已经在现有基础设施中使用的云服务提供商,但如果你刚开始云计算之旅,我们建议你对各大公有云进行深入的基准测试分析,看看哪个更适合你的业务。

本书中的实际练习将使用 AWS 和弹性 Kubernetes 服务EKS)。在前一章中,我们已经解释了基础设施设计原则,始终倾向于选择托管服务而非自管理的服务,选择 EKS 而不是在 AWS 上构建自管理集群时也适用这一原则。

选择集群和节点大小

在规划集群时,你需要决定集群和节点的大小。这个决策应基于你工作负载的预估利用率,你可以根据旧基础设施提前了解这些信息,或者可以大致计算并在生产环境上线后进行调整。在任何情况下,你都需要先决定初始的集群和节点大小,然后不断调整它们,直到达到正确的利用率水平,以实现成本和可靠性之间的平衡。除非你有充分的理由使用不同的利用率水平,否则你可以设定目标利用率为 70%到 80%之间。

这些是你可以单独或组合考虑的常见集群和节点大小选择:

  • 少量的大集群:在这种配置中,你部署了少数几个大集群。这些集群可以是生产集群或非生产集群。大集群在节点大小、节点数量或两者方面可能都较大。大集群通常更易于管理,因为它们数量较少。它们在成本上也更具效率,因为你可以实现更高的每节点和每集群的资源利用率(假设你正在运行适量的工作负载),这种提高的利用率来自于节省系统管理所需的资源。然而,大集群缺乏对多租户的硬隔离,因为你只能使用命名空间在租户之间实现软隔离。它们还会为你的生产环境引入单点故障(尤其是当你运行单个集群时)。还有另一个限制,由于任何 Kubernetes 集群最多只能管理 5000 个节点,如果你运行大量的 Pod,那么当你使用单个集群时,可能会遇到这个上限。

  • 多个小集群:在这种配置中,你部署了大量的小集群。这些集群可能在节点大小、节点数量或两者方面都较小。小集群在安全性方面表现优越,因为它们能够在资源和租户之间提供严格的隔离,同时为有多个团队和部门的组织提供强大的访问控制。它们还能减少故障的影响范围,并避免出现单点故障。然而,小集群也带来了操作上的开销,因为你需要管理一组集群。它们在资源使用效率方面也较低,因为你无法像大集群那样实现高效的资源利用率,此外,由于需要更多的控制平面资源来管理这组小集群,而这些小集群管理的工作节点总数与大集群相同,所以也增加了成本。

  • 大节点:这涉及集群中节点的大小。当你在集群中部署大节点时,你将更高效地利用节点(假设你部署的工作负载利用了节点 70-80%的资源)。这是因为大节点能够应对应用程序的波动,并能处理高 CPU/内存要求的应用程序。除此之外,一个充分利用的大节点通常会带来成本节约,因为它减少了系统管理所需的整体集群资源,并且你可以从云服务商处以折扣价格购买这种类型的节点。另一方面,大节点可能会引入较大的故障波及范围,从而影响集群和应用程序的可靠性。另外,在扩容时向集群中添加一个新的大节点会带来许多可能不需要的额外成本,因此,如果你的集群在短时间内遇到多次弹性扩展事件,大节点可能不是一个好的选择。再者,Kubernetes 在单个节点上运行的 Pods 数量有上限,无论节点的类型和大小如何,若是大节点,这一限制可能会导致资源的低效利用。

  • etcdkube-proxy等的管理成本高于管理相同计算能力的较大节点,此外,小节点每个节点的 Pods 限制较低。

  • 集中的集群与分散的集群:组织通常采用这两种方式之一来管理它们的 Kubernetes 集群。

    在分散的方式中,组织内的团队或个人可以创建并管理他们自己的 Kubernetes 集群。这种方法为团队提供了灵活性,允许他们最大限度地利用集群,并根据需求进行定制;另一方面,这也增加了操作开销、云成本,并使得在集群间执行标准化、安全性、最佳实践和工具的统一变得更加困难。这种方法更适合那些高度分散的组织,或者在进行云转型、产品生命周期过渡期,或探索和创新新技术和解决方案的组织。

    在集中的方法中,团队或个人共享一个单一的集群或一小组相同的集群,这些集群使用类似的标准、配置和服务。这种方法克服并减少了分散模型的缺点;然而,它可能会缺乏灵活性,减缓云转型,并降低团队的敏捷性。这种方法更适合那些在向成熟、平台稳定性、提高云成本削减、执行并推广标准与最佳实践以及更注重产品而非底层平台方面努力的组织。

一些组织可以从上述替代方案中运行混合模型,例如根据应用需求,拥有大、中、小节点来充分利用每种类型的优点。然而,我们建议您进行实验,决定哪种模型最适合您的工作负载性能,并且能够实现云成本降低目标。

选择集群部署和管理工具

在 Kubernetes 的早期,我们曾经从零开始部署,它通常被称为Kubernetes 硬核方式。时至今日,Kubernetes 社区发展壮大,涌现出许多工具来自动化部署。这些工具的种类从简单的自动化到完整的一键部署不等。

在本书的上下文中,我们不会详细解释市场上所有这些工具(它们有很多),也不会进行比较和基准测试。然而,我们将提出我们的选择,并简要说明选择的理由。

基础设施提供

当您第一次部署 Kubernetes 时,很可能会使用一个命令行工具,通过单一命令来部署集群,或者您可能会使用云提供商的网页控制台来进行部署。无论哪种方式,这种方法适用于实验和学习目的,但当涉及到在生产和开发环境中的实际实施时,部署工具就成为了必须。

大多数考虑部署 Kubernetes 的组织,已经拥有现有的云基础设施,或者正在进行云迁移过程。这使得 Kubernetes 并非他们将使用的唯一云基础设施。因此,我们更倾向于选择一个能够实现以下目标的部署工具:

  • 它可以用来提供 Kubernetes 以及其他基础设施组件(数据库、文件存储、API 网关、无服务器架构、监控、日志记录等)。

  • 它支持并增强了基础设施即代码(IaC)原则。

  • 它是一个与云平台无关的工具。

  • 它已被其他公司和团队在生产环境中经过实际验证。

  • 它有社区支持和活跃的开发。

我们在 Terraform 中找到了这些特点,这也是我们选择在我们管理的生产集群中使用它,并在本书的实践练习中使用它的原因。我们也强烈推荐您使用 Terraform,但如果您更喜欢其他的部署工具,您可以跳过本章,继续阅读本书,并应用相同的概念和最佳实践。

配置管理

Kubernetes 配置本质上是声明式的,因此,在部署集群之后,我们需要管理其配置。已部署的附加组件为各种功能领域提供服务,包括网络、安全、监控和日志记录。这也是为什么在您的工具集中需要一个可靠且多功能的配置管理工具。

以下是一些可靠的选择:

  • 常规的配置管理工具,如 Ansible、Chef 和 Puppet

  • Kubernetes 特定的工具,如 Helm 和 Kustomize

  • Terraform

我们推荐的适用工具顺序如下:

  1. Ansible

  2. Helm

  3. Terraform

我们可以讨论这个顺序,我们认为这些工具中的任何一个都可以满足 Kubernetes 集群的配置管理需求。然而,我们更倾向于使用 Ansible,因为它具有多功能性和灵活性,既可以用于 Kubernetes,也可以用于其他环境中的配置管理需求,这使得它比 Helm 更具优势。另一方面,Ansible 比 Terraform 更受青睐,因为它本质上是一个供应工具,虽然它可以处理配置管理,但它并不是做配置管理的最佳工具。

在本书的动手练习中,我们决定使用 Ansible 结合 Kubernetes 模块和 Jinja2 模板。

决定集群架构

每个组织都有自己管理云账户的方式。然而,我们建议至少拥有两个 AWS 账户,一个用于生产环境,另一个用于非生产环境。生产 Kubernetes 集群放置在生产账户中,非生产 Kubernetes 集群放置在非生产账户中。这种结构在安全性、可靠性和操作效率方面更为优越。

基于我们在前几节中做出的技术决策和选择,我们提出了以下适用于本书中使用的 Kubernetes 集群的 AWS 架构,您也可以使用该架构来部署您自己的生产和非生产集群:

图 2.1 – 集群架构图

图 2.1 – 集群架构图

在之前的架构图中,我们决定进行如下操作:

  • 为集群网络创建一个独立的 VPC;我们选择了无类域间路由CIDR)范围,该范围具有足够的 IPv4 地址容量,能够支持未来的扩展。每个 Kubernetes 节点、Pod 和服务都会拥有自己的 IP 地址,我们需要记住的是,服务的数量将会增加。

  • 创建公有和私有子网。公有子网中放置公共可访问资源,如负载均衡器和堡垒机,私有子网中放置私有可访问资源,如 Kubernetes 节点、数据库和缓存。

  • 为了实现高可用性,我们在三个不同的可用区中创建了资源。我们在每个可用区内放置了一个私有子网和一个公有子网。

  • 为了扩展,我们运行多个 EKS 节点组。

我们将在下一章节讨论这些设计规格的细节,以及集群架构中其他技术方面的内容。

总结

使用现代工具和托管云服务,部署一个 Kubernetes 集群可能只需要 5 分钟;然而,这远远不是一个生产级的 Kubernetes 基础设施,只适用于教育和试验。构建一个生产级的 Kubernetes 集群需要在设计和架构底层基础设施、集群以及上面运行的核心服务方面付出艰苦的努力。

到目前为止,您已经了解了在设计、构建和操作 Kubernetes 集群时需要考虑的不同方面和挑战。我们探索了部署 Kubernetes 集群的不同架构选择,以及与此过程相关的重要技术决策。然后,我们讨论了提出的集群设计,这将作为本书中实践练习的基础,并重点介绍了我们选择的基础设施平台、工具和架构。

在下一章中,我们将学习如何将所有内容整合在一起,使用本章讨论的设计概念编写基础设施即代码(IaC),并使用 Terraform 遵循行业最佳实践来配置我们的第一个 Kubernetes 集群。

进一步阅读

欲了解本章涉及的更多信息,请参考以下链接:

第三章:第三章:使用 AWS 和 Terraform 配置 Kubernetes 集群

在上一章中,我们了解了 Kubernetes 集群和基础设施设计,以及如何创建一个部署架构来实现最佳实践和标准。在设计和构建 Kubernetes 平台时有多种选择。选择一个适合你用例并满足生产就绪目标的解决方案并不是一件容易的事。Kubernetes、其底层技术以及周围的生态系统仍然面临着挑战和局限性。

在本章中,我们将详细讲解基础设施设计的实现过程。基本上,我们将学习如何使用 Terraform 声明性地创建 Kubernetes 基础设施。在配置基础设施的过程中,我们将学习一些实现最佳实践,例如将基础设施组件封装成可重用的模块、在不增加运维开销和复杂度的情况下按环境分离 Kubernetes 集群。此外,你还将通过简单的 Terraform 命令,实践如何部署第一个 Kubernetes 集群及集群组。

本章将涵盖以下主题:

  • 实现原则和最佳实践

  • 集群部署和发布策略

  • 准备 Terraform

  • 创建网络基础设施

  • 创建集群基础设施

  • 清理并销毁基础设施资源

技术要求

本章需要提前安装 Terraform 工具。

除了这个工具,你还需要准备好 AWS 账户和用户凭证。请确保 AWS CLI 已用你的 AWS 凭证进行了身份验证。你可以参考 AWS 文档获取更多说明,网址为docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html

本章的代码位于github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter03

请访问以下链接查看《代码实践》视频:

bit.ly/39Ocoyq

安装 Terraform

Terraform 二进制文件是一个命令行工具,用于开发基础设施即代码IaC),并规划和执行资源的创建,管理基础设施提供商,如 AWS、Azure、GCP、Fastly、OKTA 等。

你可以根据官方文档的说明,在www.terraform.io/downloads.html下载最新版本的 Terraform。

安装完 Terraform 后,你就可以开始进行接下来的实践操作。

实现原则和最佳实践

第一章Kubernetes 基础设施与生产就绪性简介 中,你了解了本书中我们将遵循的 12 条基础设施设计原则。我想通过强调推动我们实施集群基础设施的原则来开始本章。以下是影响本章实现决策的三个原则:

  1. 基础设施即代码:在本章中,你将以声明方式编写每一段基础设施代码。你将通过使用 Terraform 来实现这一目标。

  2. Go 管理:创建 Kubernetes 集群有两种基本方式——要么自己构建并操作 Kubernetes 控制平面和工作节点(在本地或云上),要么使用云中的 托管 Kubernetes 服务,如 Google Kubernetes Engine (GKE)、Azure Kubernetes Service (AKS) 和 AWS Elastic Kubernetes Service (EKS)。在本书中,我将使用 EKS,因为它符合 托管服务 原则。

  3. 标准化:当我们选择 Terraform 作为我们的配置和基础设施即代码(IaC)工具时,我们应用了这一原则。Terraform 并不是启动 Kubernetes 集群的最简单方式,且有其他工具可能更快速且易于学习。然而,我们需要将我们的基础设施工具集标准化,尽量使用更少的工具。因此,Terraform 是合适的选择,因为在大多数使用场景中,你的生产环境并不仅仅是 Kubernetes。还有数据库、缓存服务、内容分发、负载均衡等。这些类型的基础设施组件更容易通过 Terraform 创建和管理。

集群部署和推广策略

在前一章中,我们探讨了不同的基础设施设计备选方案、限制和边角情况。我们做出了满足生产级 Kubernetes 集群基础设施设计原则的架构决策。最后,我们提出了一个完整的 Kubernetes 基础设施部署架构,我们将在本书中构建并使用。当然,在从一章过渡到下一章的过程中,我们将不断完善我们的基础设施设计和实现,增加更多功能,使其更好。

在实现方面,我们应该解决如何部署集群并进行推广的问题。具体来说,我们追求的是可扩展性、简洁性和操作效率。在接下来的章节中,我们将遵循这些原则:

  1. 开发通用基础设施模块:通过将每个基础设施资源封装到可重用的代码模块中,这将使我们能够通过最小或零代码更改来自动化集群的配置。这也促进了代码重用的实践,这对于简化基础设施即代码(IaC)至关重要,并提高了操作效率。

  2. 支持单集群和多集群:在实际应用中,Kubernetes 部署团队需要多个集群来为整个公司或特定产品提供服务。在本章中,我们将采取一种策略,使用相同的基础设施代码和配置创建一组集群。此外,我们还将创建多个不同配置的集群组。这将帮助我们为多个生产和非生产集群提供服务,并自动化其配置和操作。该实现具有可扩展性,因为我们可以根据底层 IaaS 提供商的限制来配置多个集群,而无需扩展基础设施团队。

  3. 通过最小化更改分离生产环境和非生产环境:推荐的做法之一是为生产环境和非生产环境分别使用两个 AWS 账户,我们的实现也支持这种模型,且仅需最小的代码更改和管理工作。

  4. 自动化基础设施部署:每一块基础设施都由 Terraform 管理,通过少量的命令,我们就可以配置整个 Kubernetes 集群。我们可以使用传统的 CI/CD 工具(如 Jenkins)来构建自动化的基础设施部署和测试流水线。

实际上,集群部署并非一次性任务。它是一个持续的过程,影响集群的质量、稳定性和运营,此外,还会影响其上运行的产品和服务。因此,我们希望建立一个稳固的基础设施部署策略,我们将在本章的实现过程中遵循这一策略,并在全书中持续改进。

准备 Terraform

在创建 Kubernetes 集群的 Terraform 配置和代码之前,你需要为基础设施创建一个新的源代码仓库,并创建 Terraform 目录结构。此外,你还将学习如何配置和使用 Terraform 的共享状态,这是在生产环境中管理基础设施即代码(IaC)的一个关键最佳实践。

Terraform 目录结构

Terraform 目录是你在源代码仓库中存放所有 Terraform 源代码的地方。我建议创建一个独立的源代码仓库,该仓库应包含所有基础设施代码和配置。以下是我们将在接下来的章节中开发的 Terraform 源代码目录结构:

图 3.1 – Terraform 目录结构

图 3.1 – Terraform 目录结构

持久化 Terraform 状态

Terraform 会保存其管理下的基础设施资源的状态,以便能够将其映射到实际世界中的现有资源。默认情况下,状态存储在本地文件中。然而,这不推荐用于生产规模的基础设施,因为在这种情况下,保持一致的状态以及在分布式团队成员之间共享状态是至关重要的。

作为推荐的 Terraform 最佳实践,你应该配置 Terraform 使其保持远程状态并加锁:

  • 远程:由于你已经使用 AWS 作为基础设施提供商,你可以利用 S3 存储桶远程存储 Terraform 状态文件。

  • 锁定:你可以通过使用 DynamoDB 表来实现 Terraform 状态锁定。然后,Terraform 状态将在当前用户操作完成之前锁定,直到此用户完成操作,其他用户才能获得锁定。

创建 Terraform 状态配置

按照以下步骤创建 Terraform 目录结构以及共享状态配置的目录:

  1. 创建一个名为 terraform 的根目录。这是所有 Terraform 源代码的根目录。

  2. 创建一个名为 shared-state 的子目录。这个目录将包含用于配置 S3 存储桶和 DynamoDB 表的 Terraform 源代码。这两个资源都用于存储共享状态。

在接下来的步骤中,你将在 shared-state 目录下创建共享状态的 Terraform 代码,目录结构如下:

图 3.2 – 共享状态目录结构

图 3.2 – 共享状态目录结构

重要说明

你可以在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter03/terraform/shared-state 找到共享状态 Terraform 配置的完整源代码。

现在,让我们在 shared-state 目录下创建 Terraform 文件:

  1. Terraform 可以创建和管理云端以及本地的基础设施资源,它可以通过与外部基础设施的通信来实现这一点,使用的提供商是一种软件插件,可以将 Terraform 命令翻译为基础设施提供商可以理解和执行的 API。

  2. config.tf 文件中,你定义了在本章中将使用的提供商配置。对于每个提供商,你需要定义其名称以及你打算使用的版本。要了解如何定义“所需提供商版本”,请访问 Terraform 官方文档

    明确地定义版本非常重要,特别是在 Terraform 被多个用户或自动化工具使用时。这是为了避免升级到可能会破坏 Terraform 状态的新版:

    terraform {
      required_version = "~> 0.14.5"
    }
    

    这个代码块定义了 AWS 提供商的配置。你只需要指定 AWS 区域和提供商的版本:

    provider "aws" {
      region = var.aws_region
      version = "~> 3.27.0"
    }
    
  3. terraform.tfvars文件中,你定义了 Terraform 在配置基础设施资源时需要使用的环境变量。使用 Terraform tfvars文件是将环境变量传递给 Terraform 的良好实践。这样做能让你把所有配置,包括环境变量,版本化存储在源代码控制中,作为你的“真实来源”:

    aws_region            = "us-east-1"
    clusters_name_prefix = "packtclusters"
    

    我们使用us-east-1作为默认的 AWS 区域,但你可以使用任何其他区域,只要你在其他练习中保持一致。

    第二个环境变量是集群名称前缀,你将使用它为你的集群命名,将它们识别为一组集群。这个名称前缀可以代表你公司的名称或产品的名称,当然,你可以自由使用任何合适的命名约定。

  4. variables.tf文件中,你定义了 Terraform 代码将使用的输入变量。你将在本章的练习中使用两个输入变量,第一个是 AWS 区域,第二个是集群名称前缀。这两个变量的值都来自于前面的terraform.tfvars文件:

    variable "aws_region" {
      type = string
    }
    variable "clusters_name_prefix" {
      type = string
    }
    
  5. tf-state-s3.tf文件中,你定义了两个 S3 存储桶资源。第一个存储桶存储 VPC 和网络资源的状态,第二个存储桶存储 Kubernetes 集群资源的状态,如 EKS 和工作节点组。

    以下代码片段使用了一个名为aws_s3_bucket的 Terraform 资源,这是 Terraform AWS 提供程序中的内置资源,可用于创建 AWS S3 存储桶并设置其配置参数。

    我们将使用这个 S3 存储桶来持久化 Terraform 状态。正如你在以下代码中所看到的,这个 S3 存储桶有私有访问权限,以确保它不被公众访问。它还启用了删除保护,以防止其被意外删除:

    resource "aws_s3_bucket" "clusters_tf_state_s3_bucket" {
      bucket = "${var.clusters_name_prefix}-terraform-state"
      acl    = "private"
      versioning {
        enabled = true
      }
      lifecycle {
        prevent_destroy = true
      }
      tags = {
        Name      = "${var.clusters_name_prefix} S3 Remote Terraform State Store"
        ManagedBy = "terraform"
      }
    }
    

    代码的第二部分与之前的部分类似,但它用于创建网络基础设施或虚拟私有云VPC)资源状态的 S3 存储桶:

    resource "aws_s3_bucket" "clusters_vpc_tf_state_s3_bucket" {
      bucket = "${var.clusters_name_prefix}-vpc-terraform-state"
      acl    = "private"
      versioning {
        enabled = true
      }
      lifecycle {
        prevent_destroy = true
      }
      tags = {
        Name      = "${var.clusters_name_prefix} VPC S3 Remote Terraform State Store"
        ManagedBy = "terraform"
      }
    }
    

    将基础设施状态分为两个文件,如我们在前面的代码中所做的,值得商榷。然而,我们倾向于使用一种平衡的方法,除非资源的生命周期与 Kubernetes 集群独立,否则我们不会为资源使用单独的状态。这种分离有助于资源的变更管理,并且将关键资源解耦。

  6. tf-state-dynamodb.tf文件中,你创建了两个 DynamoDB 表,第一个用于 VPC 资源状态锁定,第二个用于 Kubernetes 集群资源。

    以下代码片段使用了一个名为aws_dynamodb_table的 Terraform 资源,这是 Terraform AWS 提供程序中的内置资源,用于创建 AWS DynamoDB 表并设置其配置参数。

    这段代码创建了一个 DynamoDB 表,用于存储 Kubernetes 集群资源的共享 Terraform 状态的锁。这个锁将防止多个并行执行的操作对相同的状态文件或相同资源进行修改,从而防止用户同时对基础设施进行变更。这样做可能会非常危险,对吧?

    resource "aws_dynamodb_table" "clusters_dynamodb_tf_state_lock" {
      name           = "${var.clusters_name_prefix}-terraform-state-lock-dynamodb"
      hash_key       = "LockID"
      read_capacity  = 20
      write_capacity = 20
      attribute {
        name = "LockID"
        type = "S"
      }
    }
    

    tf-state-dynamodb.tf 文件的第二部分创建了一个 DynamoDB 表,用于存储 VPC 资源的共享 Terraform 状态的锁:

    resource "aws_dynamodb_table" "clusters_vpc_dynamodb_tf_state_lock" {
      name           = "${var.clusters_name_prefix}-vpc-terraform-state-lock-dynamodb"
      hash_key       = "LockID"
      read_capacity = 20
      write_capacity = 20
      attribute {
        name = "LockID"
        type = "S"
      }
    }
    

当你应用之前的 Terraform 代码文件时,它将创建两个 DynamoDB 表。在接下来的章节中,我们将学习如何配置 Terraform 来使用它们。然后,Terraform 将能够为其共享状态文件创建锁。

配置 Terraform 状态

在为共享状态资源创建了之前的 Terraform 代码文件之后,你需要执行以下操作来在你的 AWS 账户中配置资源:

  1. 初始化 Terraform 状态:

    $ cd Chapter03/terraform/shared-state
    $ terraform init
    Initializing modules...
    Initializing the backend...
    Initializing provider plugins...
    - Checking for available provider plugins...
    - Downloading plugin for provider "aws" (hashicorp/aws) 3.27.0...
    Terraform has been successfully initialized!
    You may now begin working with Terraform. Try running "terraform plan" to see
    any changes that are required for your infrastructure. All Terraform commands
    should now work.
    If you ever set or change modules or backend configuration for Terraform,
    rerun this command to reinitialize your working directory. If you forget, other
    commands will detect it and remind you to do so if necessary.
    
  2. 运行 terraform plan 命令,验证计划的更改,然后再应用它们:

    $ terraform plan
    
  3. terraform plan 命令成功完成后,你将得到如下输出。需要添加四个资源——两个 S3 存储桶和两个 DynamoDB 表:图 3.3 – Terraform plan 命令输出

    图 3.3 – Terraform plan 命令输出

  4. 执行 terraform apply 命令。当系统提示确认执行时,输入 yes

    $ terraform apply
    
  5. terraform apply 命令成功完成后,你将得到如下输出。到那时,Terraform 已成功创建了四个 AWS 资源:

图 3.4 – Terraform apply 命令输出

图 3.4 – Terraform apply 命令输出

现在你已完成 AWS 资源的配置,以持久化和管理 Terraform 的共享状态。在接下来的章节中,你将学习如何配置 VPC 和其他网络资源,以运行你的第一个 Kubernetes 集群。

使用 Terraform 工作区

在上一节中,你学习了 Terraform 配置有一个后端,它定义了操作是如何执行的,以及基础设施状态存储的位置,比如 S3 存储桶。Terraform 使用工作区来组织和隔离单个后端下的多个状态。

当用户希望运行多个相同基础设施的实例,而不创建多个后端和状态文件时,这个概念变得非常有用。假设你想使用 Terraform 来配置一个 Kubernetes 集群 ClusterA,并希望使用相同的配置来配置第二个集群 ClusterB。在这种情况下,工作区提供了一种开箱即用且可扩展的解决方案,因为你将能够为所有集群(N 个集群)使用单一后端,但在每个工作区中为每个集群配置独立的状态文件。

如果你有一个名为 k8s_s3_backend 的 Terraform 配置后端,并且你希望使用相同的 Terraform 基础代码配置 N 个 Kubernetes 集群,那么你可以执行以下操作:

$ terraform workspace new cluster1
Created and switched to workspace "cluster1"!
You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.
$ terraform apply 
<apply outputs>

然后,对每个N集群重复相同的过程:

$ terraform workspace new clusterN
Created and switched to workspace "clusterN"!
You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.
$ terraform apply 
<apply outputs>

创建网络基础设施

第二章,《构建生产级 Kubernetes 基础设施》中,你详细了解了基础设施架构设计建议以及在 Kubernetes 集群的生产就绪状态方面应该做出的技术决策。在本节中,你将使用 Terraform 来配置 Kubernetes 生产基础设施的网络层。

这些是你将在本节中使用 Terraform 代码配置的 AWS 网络资源:

  • AWS VPC

  • 私有子网

  • 公有子网

  • 路由表

  • Internet 和 NAT 网关

将 AWS 资源封装为可重用的代码模块是一种推荐的 IaC 实践。在接下来的小节中,你将创建一个包含之前 AWS 资源的 VPC Terraform 模块。然后,你可以重复使用该模块,无需修改代码,便可为多个 Kubernetes 集群配置 VPC。

开发 VPC Terraform 模块

terraform根目录下,创建一个名为modules的目录。然后,创建一个名为eks-vpc的子目录。该子目录将包含以下 Terraform 代码文件:

  • variables.tf

  • main.tf

  • outputs.tf

输入变量

这些是该模块接受的输入变量。模块的用户应该为这些变量提供相应的值:

  • 10.40.0.0/17

  • 1 或其他前缀,如 10.40.64.0/20

  • 1 或其他前缀,如 10.40.0.0/20

  • 集群名称前缀:用于命名 VPC 资源的集群名称前缀的值。

  • 常见标签:任何你想要分配给 VPC 资源的 AWS 标签,以帮助后续识别和分类它们。

variables.tf文件定义如下:

variable "eks_vpc_block" {
  type = string
}
variable "eks_private_subnets_prefix_list" {
  type = list(string)
}
variable "eks_public_subnets_prefix_list" {
  type = list(string)
}
variable "clusters_name_prefix" {
  type = string
}
variable "common_tags" {
  type = map(string)
}

前面的代码片段定义了五个 Terraform 变量块及其所有类型字符串。在创建集群 VPC部分,你将使用这个 VPC 模块并学习如何传递这些变量的值。

模块主资源

main.tf文件定义了创建 Kubernetes AWS 网络组件所需的网络资源,包括公有和私有子网、互联网和 NAT 网关以及路由表。

以下代码片段使用了名为aws_vpc的 Terraform 资源,这是 Terraform AWS 提供程序中的内置资源,可用于创建 AWS VPC 并设置其配置参数。

在下面的代码块中,你定义了 VPC 资源和一个数据资源,该数据资源用于检索你在main.tf文件中使用的 AWS 可用区的值:

resource "aws_vpc" "eks_vpc" {
  cidr_block           = var.eks_vpc_block
  enable_dns_hostnames = true
  tags = merge(
    var.common_tags,
    {
      Name = "${var.clusters_name_prefix}-vpc"
    },
  )
  lifecycle {
    ignore_changes = [
      tags
    ]
  }
}
data "aws_availability_zones" "availability_zones" {
}

以下代码片段使用了名为aws_subnet的 Terraform 资源,这是 Terraform AWS 提供程序中的内置资源,可用于创建 AWS 子网并设置其配置参数。

这段代码使用了 Terraform 内置的 count 结构,根据私有子网前缀的数量来创建一个或多个子网:

resource "aws_subnet" "eks_private_subnets" {
  count             = length(var.eks_private_subnets_prefix_list)
  cidr_block        = element(var.eks_private_subnets_prefix_list, count.index)
  vpc_id            = aws_vpc.eks_vpc.id
  availability_zone = data.aws_availability_zones.availability_zones.names[count.index]
  tags = merge(
    var.common_tags,
    {
      Name = "eks-private-${var.clusters_name_prefix}-${data.aws_availability_zones.availability_zones.names[count.index]}"
    },
  )
  lifecycle {
    ignore_changes = [
      tags
    ]
  }
}

main.tf 文件的其余部分,你定义了一个 aws_subnet 资源,类似于私有子网资源,但这是为公共子网设计的。同时,你还会创建补充的 VPC 网络资源,处理路由,将子网连接在一起并与互联网连接,比如 NAT 和互联网网关、路由表、以及 NAT IP。你可以在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter03/terraform/modules/eks-vpc/main.tf 找到完整的源代码。

输出值

outputs.tf 文件定义了 VPC 模块的输出值。Terraform 需要这些值,将其作为 Kubernetes 集群模块的输入,以便在你进行集群部署时使用。VPC 模块有四个输出:VPC ID;私有子网 ID;公共子网 ID;和 NAT IP。

outputs.tf 文件定义如下:

output "eks_cluster_vpc_id" {
  value = aws_vpc.eks_vpc.id
}
output "eks_private_subnet_ids" {
  value = aws_subnet.eks_private_subnets.*.id
}
output "eks_public_subnet_ids" {
  value = aws_subnet.eks_public_subnets.*.id
}
output "eks_nat_ips" {
  value = aws_eip.eks_nat_ips.*.public_ip
}

上述代码片段定义了五个 Terraform 输出块。在 部署集群 部分,你将把这些输出作为输入,传递给 Kubernetes Terraform 模块。

开发集群 VPC

terraform 根目录下,创建一个目录并命名为 packtclusters-vpc。该目录将包含以下 Terraform 代码文件:

  • config.tf

  • terraform.tfvars

  • variables.tf

  • main.tf

  • outputs.tf

前面列出的 Terraform 文件构成了你的 Kubernetes 集群 VPC。你将在接下来的子部分中了解每个代码和配置文件的详细内容。

配置

config.tf 包含了 Terraform 共享状态配置和 AWS 提供程序定义:

terraform {
  backend "s3" {
    bucket         = "packtclusters-vpc-terraform-state"
    key            = "packtclusters-vpc.tfstate"
    region         = "us-east-1"
    dynamodb_table = "packtclusters-vpc-terraform-state-lock-dynamodb"
  }
  required_version = "~> 0.14.5"
  required_providers {
    aws = "~> 3.27"
  }
}
provider "aws" {
  region  = var.aws_region
  version = "~> 3.27"
}

上述代码块告诉 Terraform 使用哪个 S3 存储桶来持久化状态,并指定了 Terraform 和 AWS 提供程序的版本。

环境变量

terraform.tfvars 文件定义了输入变量的值。这些值是 VPC 模块所需的,用于设置这些输入的值:AWS 区域;VPC IP CIDR;私有子网前缀列表;以及公共子网前缀列表。

terraform.tfvars 文件定义如下:

aws_region           = "us-east-1"
clusters_name_prefix = "packtclusters"
vpc_block            = "10.40.0.0/17"
public_subnets_prefix_list = [
  "10.40.0.0/20",
  "10.40.16.0/20",
  "10.40.32.0/20",
]
private_subnets_prefix_list = [
  "10.40.64.0/20",
  "10.40.80.0/20",
  "10.40.96.0/20",
]

对于上述代码,你可以根据网络拓扑和应用需求选择不同的 CIDR 块作为 VPC IP 范围,以及不同的子网前缀。

重要说明

你应该确保 VPC CIDR 不被你自己 AWS VPC 中的其他 VPC 使用,以避免 IP 冲突。你还需要确保 VPC CIDR 拥有足够数量的 IP 地址,超过你 Kubernetes 集群中预期的最大 pod 数量。

输入变量

variables.tf 文件定义了 Terraform 在创建 VPC 模块资源时将使用的五个输入变量。它与之前的 variables.tf 文件非常相似。您可以在此处查看其完整源代码:github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter03/terraform/packtclusters-vpc/variables.tf

集群 VPC

main.tf 文件包含两个代码块:vpc 模块块,它创建一个 eks-vpc 模块的实例,以及 locals 代码块,它定义了要分配给 VPC 资源的 common_tags

main.tf 文件定义如下:

locals {
  common_tags = {
    ManagedBy = "terraform"
  }
}
module "vpc" {
  source                          = "../modules/eks-vpc"
  clusters_name_prefix            = var.clusters_name_prefix
  eks_vpc_block                   = var.vpc_block
  eks_public_subnets_prefix_list  = var.public_subnets_prefix_list
  eks_private_subnets_prefix_list = var.private_subnets_prefix_list
  common_tags                     = local.common_tags
}

多亏了 Terraform 模块,这使得前面的代码变得简洁清晰,因为它隐藏了创建 AWS VPC 的复杂性。在下一小节中,您将创建在创建集群 VPC 时使用的 Terraform 输出。

输出值

outputs.tf 文件定义了在创建集群 VPC 后需要获取的输出值。这些输出值包括 VPC ID、私有子网 ID 和公共子网 ID。

outputs.tf 文件定义如下:

output "vpc_id" {
  value = module.vpc.eks_cluster_vpc_id
}
output "private_subnet_ids" {
  value = module.vpc.eks_private_subnet_ids
}
output "public_subnet_ids" {
  value = module.vpc.eks_public_subnet_ids
}

上一个代码块的输出作为下一节 Kubernetes 集群 Terraform 模块的输入。

部署集群 VPC

一旦您完成了前面章节中 VPC Terraform 文件的开发,您现在可以部署 VPC 资源并将其创建到您的 AWS 账户中:

  1. 初始化 Terraform 状态:

    $ cd Chapter03/terraform/packtclusters-vpc
    $ terraform init
    Initializing modules...
    - vpc in ../../modules/eks-vpc
    Initializing the backend...
    Initializing provider plugins...
    - Checking for available provider plugins...
    - Downloading plugin for provider "aws" (hashicorp/aws) 3.27.0...
    Terraform has been successfully initialized!
    You may now begin working with Terraform. Try running "terraform plan" to see
    any changes that are required for your infrastructure. All Terraform commands
    should now work.
    If you ever set or change modules or backend configuration for Terraform,
    rerun this command to reinitialize your working directory. If you forget, other
    commands will detect it and remind you to do so if necessary.
    
  2. 执行 terraform plan 命令,查看计划中的更改,然后再应用它们:

    terraform plan command. There are 28 resources in the Terraform plan, and when you execute the terraform apply command, these 28 resources will be created in your AWS account:![Figure 3.5 – The terraform plan command output    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_03_005.jpg)Figure 3.5 – The terraform plan command output
    
  3. 执行 terraform apply 命令。在提示确认执行时,输入 yes

    $ cd Chapter03/terraform/packtclusters-vpc
    $ terraform apply
    
  4. terraform apply 命令成功完成后,您将获得以下输出,此时 Terraform 已成功创建了 28 个网络资源:

![图 3.6 – terraform apply 命令输出]

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_03_006.jpg)

图 3.6 – terraform apply 命令输出

完成本节后,您应该已经成功在 AWS 账户中创建了 Kubernetes 集群 VPC 及其网络组件。现在,它已经准备好部署在其上的集群,您将在下一节中学习如何操作。

创建集群基础设施

在本节中,您将开发以下 Terraform 模块:

  • 一个 EKS 模块

  • 一个 Kubernetes 工作节点模块

  • 一个封装了 EKS 控制平面和工作节点的 Kubernetes 集群模块

然后,您将使用这些模块来 Terraform 您的第一个集群 Packt cluster,并将其部署到您的 AWS 账户中。

开发 EKS Terraform 模块

terraform/modules 目录下,创建一个名为 eks-cp 的子目录。该目录将包含以下 EKS 控制平面模块的 Terraform 源代码文件:

  • variables.tf

  • main.tf

  • security-groups.tf

  • iam.tf

  • outputs.tf

上述文件列表共同构成了 EKS Terraform 模块。你将在接下来的子章节中学习这些代码和配置文件的详细内容。

输入变量

variables.tf 文件定义了在 EKS 模块中接受的输入变量。模块用户应提供这些变量的值:

  • 完整的集群名称

  • 集群 Kubernetes 版本

  • VPC ID

  • 私有子网 ID

  • 公共子网 ID

  • 常见标签

这个文件类似于你在 VPC 模块中创建的 variables.tf 文件。你可以在这里查看它的完整源代码:github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter03/terraform/modules/eks-cp/variables.tf

模块主资源

main.tf 文件定义了配置和创建 EKS 所需的资源。这些资源包括集群名称、版本和集群 IAM 角色 ARN。

以下代码片段使用了一个名为 aws_eks_cluster 的 Terraform 资源,这是 Terraform AWS 提供程序中的内建资源,可以用来创建一个 AWS EKS 集群并设置其配置参数。

main.tf 文件定义如下:

resource "aws_eks_cluster" "eks_cluster" {
  name     = var.cluster_full_name
  version  = var.cluster_version
  role_arn = aws_iam_role.eks_cluster_role.arn
  vpc_config {
    security_group_ids = [aws_security_group.eks_cluster_sg.id]
    subnet_ids         = concat(var.private_subnets, var.public_subnets)
  }
  depends_on = [
    aws_iam_role_policy_attachment.eks_clusterrole_policy_attachment,
    aws_iam_role_policy_attachment.eks_servicerole_policy_attachment,
  ]
}

在之前的代码中,你会注意到 EKS 资源引用了 EKS IAM 角色和 EKS 安全组的值。这两者都在 EKS 模块中创建,但分别存放在两个不同的 Terraform 文件中,以便更好地组织和清晰代码。你将在接下来的子章节中学习如何创建 EKS 安全组和 IAM 角色。

安全组

以下代码片段使用了一个名为 aws_security_group 的 Terraform 资源,这是 Terraform AWS 提供程序中的内建资源,可以用来创建一个 AWS 安全组并设置其配置参数。

以下 security-groups.tf 文件为 EKS 控制平面定义了一个安全组:

resource "aws_security_group" "eks_cluster_sg" {
  name        = "${var.cluster_full_name}-cluster"
  description = "EKS cluster Security group"
  vpc_id      = var.vpc_id
  tags = merge(
    var.common_tags,
    {
      Name                                             = "${var.cluster_full_name}-cluster-sg"
      "kubernetes.io/cluster/${var.cluster_full_name}" = "owned"
    },
  )
}

如果你注意到,之前的安全组没有定义 ingress/egress 规则。这些规则将在集群工作节点模块中定义。

IAM 角色和策略

iam.tf 文件使用了一个名为 aws_iam_role 的 Terraform 资源,这是 Terraform AWS 提供程序中的内建资源,可以用来创建一个 AWS IAM 角色并设置其配置参数。

EKS 集群必须获得特定的策略才能正常运行:

  • AmazonEKSClusterPolicy

  • AmazonEKSServicePolicy

这些策略必须附加到我们将在下一个代码片段中创建的 EKS 集群 IAM 角色上。要了解更多关于这些策略的信息,您可以查看 EKS 官方文档:docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html

以下 iam.tf 文件定义了一个 IAM 角色,并将两个策略与该角色关联:

resource "aws_iam_role" "eks_cluster_role" {
  name = "${var.cluster_full_name}-cluster-role"
  assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "eks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
  tags = var.common_tags
}

相关的两个 IAM 策略是 AmazonEKSClusterPolicyAmazonEKSServicePolicy。它们都是 AWS 预定义的 IAM 策略:

data "aws_iam_policy" "AmazonEKSClusterPolicy" {
  arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
}
data "aws_iam_policy" "AmazonEKSServicePolicy" {
  arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy"
}
resource "aws_iam_role_policy_attachment" "eks_clusterrole_policy_attachment" {
  policy_arn = data.aws_iam_policy.AmazonEKSClusterPolicy.arn
  role       = aws_iam_role.eks_cluster_role.name
  depends_on = [data.aws_iam_policy.AmazonEKSClusterPolicy]
}
resource "aws_iam_role_policy_attachment" "eks_servicerole_policy_attachment" {
  policy_arn = data.aws_iam_policy.AmazonEKSServicePolicy.arn
  role       = aws_iam_role.eks_cluster_role.name
  depends_on = [data.aws_iam_policy.AmazonEKSServicePolicy]
}

你需要将前面代码中定义的 IAM 角色附加到 EKS 集群,以使其能够在 AWS 环境中运行。在下一节中,你将定义 EKS 模块的输出。

输出值

outputs.tf 文件定义了 EKS 模块的输出值。共有三个输出:安全组 ID;集群 证书颁发机构CA);以及集群 API 服务器端点。

outputs.tf 文件定义如下:

output "security_group" {
  value = aws_security_group.eks_cluster_sg.id
}
output "kubeconfig" {
  value = local.kubeconfig
}
output "ca" {
  value = aws_eks_cluster.eks_cluster.certificate_authority[0].data
}
output "endpoint" {
  value = aws_eks_cluster.eks_cluster.endpoint
}

在本节中,你学习了如何为 EKS 开发 Terraform 模块。你将与其他模块一起使用它,来构建你的集群基础设施。在下一节中,你将学习如何为集群工作节点开发 Terraform 模块。

开发工作节点的 Terraform 模块

terraform/modules 目录下,创建一个子目录,并命名为 eks-workers。该目录将包含以下 Terraform 代码文件:

  • variables.tf

  • main.tf

  • security-groups.tf

  • iam.tf

  • user-data.tf

  • authconfig.tf

  • outputs.tf

    重要说明

    AWS 最近推出了托管 EKS 节点组,这是一个由 EKS 服务代为管理工作节点的服务。这是一个新服务,缺少一些重要功能,如无法提供自定义用户数据,而自定义用户数据对于优化工作节点性能和 kubelet 参数至关重要。因此,推荐继续使用自管理工作节点,直到 AWS 实现此功能。

输入变量

variables.tf 文件定义了此模块所需的输入变量。工作节点模块有多个输入,例如工作节点 AMI ID、EC2 实例类型、用户数据和实例存储大小。

variables.tf 文件定义如下:

variable "workers_ami_id" {
  type = string
}
variable "workers_instance_type" {
  type = string
}
variable "workers_storage_size" {
  type = string
}

重要说明

AWS 定期发布优化过的 EKS 工作节点 AMI。要选择其中之一,请查看 EKS 文档 docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami.html

你仍然可以为 EKS 工作节点构建自己的 AMI,并且可以使用 github.com/awslabs/amazon-eks-ami 中的 EKS AMI 开源项目。

请在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter03/terraform/modules/eks-workers/variables.tf 查看其余变量和完整源代码。

模块主资源

main.tf 文件定义了工作节点的资源及其属性。该模块包含两个 AWS 资源:

  • 自动伸缩组

  • 启动模板

自动伸缩组使用启动模板,根据启动规格添加工作实例。

以下代码片段使用了名为aws_autoscaling_group的 Terraform 资源,这是 Terraform AWS 提供程序中的内置资源,可用于创建 AWS 自动缩放组并设置其配置参数。

main.tf文件定义如下:

resource "aws_autoscaling_group" "workers" {
  name                = "${var.cluster_full_name}-workers-asg-${var.workers_instance_type}"
  max_size            = var.workers_number_max
  min_size            = var.workers_number_min
  vpc_zone_identifier = var.private_subnet_ids
  launch_template {
    id      = aws_launch_template.workers.id
    version = "$Latest"
  }
}

请查看github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter03/terraform/modules/eks-workers/main.tfmain.tf源代码的其余部分。

安全组

security-groups.tf文件定义了工作节点的安全组及其控制流量的入站/出站规则,以及控制平面与工作节点之间的流量。

请参考第二章构建生产级别的 Kubernetes 基础设施,以了解有关安全组入站/出站规则和允许端口的更多详细信息。

security-groups.tf文件定义如下:

resource "aws_security_group" "workers" {
  name        = "${var.cluster_full_name}-workers"
  description = "Security group for all nodes in the ${var.cluster_full_name} cluster"
  vpc_id      = var.vpc_id
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

您可以查看github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter03/terraform/modules/eks-workers/security-groups.tf上的完整源代码。

IAM 角色和策略

iam.tf文件定义了一个 IAM 角色,并将两个策略与该角色关联:

resource "aws_iam_role" "workers" {
  name               = "${var.cluster_full_name}-workers"
  assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}

IAM 策略包括AmazonEKSWorkerNodePolicyAmazonEKS_CNI_PolicyAmazonEC2ContainerRegistryReadOnlyCloudWatchAgentServerPolicy。它们都是标准预定义的 IAM 策略:

resource "aws_iam_role_policy_attachment" "AmazonEKSWorkerNodePolicy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
  role       = aws_iam_role.workers.name
}
resource "aws_iam_role_policy_attachment" "AmazonEKS_CNI_Policy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
  role       = aws_iam_role.workers.name
}
resource "aws_iam_role_policy_attachment" "AmazonEC2ContainerRegistryReadOnly" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
  role       = aws_iam_role.workers.name
}
resource "aws_iam_role_policy_attachment" "CloudWatchAgentServerPolicy" {
  policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
  role       = aws_iam_role.workers.name
}
resource "aws_iam_instance_profile" "workers" {
  name = "${var.cluster_full_name}-workers"
  role = aws_iam_role.workers.name
}

您需要将前述代码中定义的 IAM 角色附加到工作节点,以使它们能够在 AWS 环境中运行。

用户数据

user-data.tf文件定义了在工作实例启动时执行的用户数据脚本。

以下代码片段使用了名为locals的特殊 Terraform 代码块,用于定义一组键/值配置。在我们的解决方案中,我们使用它来构建工作节点用户数据脚本。

user-data.tf文件定义如下:

locals {
  kubelet_extra_args = <<ARGS
--v=3 \
ARGS
  userdata = <<USERDATA
#!/bin/bash
set -o xtrace
/etc/eks/bootstrap.sh --b64-cluster-ca "${var.cluster_ca}" --apiserver-endpoint "${var.cluster_endpoint}" \
USERDATA
  workers_userdata = "${local.userdata} --kubelet-extra-args \"${local.kubelet_extra_args}\"  \"${var.cluster_full_name}\""
}

本书稍后将更新前述代码,以优化参数启动kubelet以调整工作节点的性能。

Worker 身份验证

Kubernetes 要求对工作节点进行身份验证,以便能够加入集群并与kube-api-server通信。EKS 提供了自己的解决方案来执行此类型的身份验证,因为它需要集群管理员创建一个包含工作节点 IAM 角色 ARN 的 ConfigMap,并将其映射到 Kubernetes 系统节点组。通过这样做,工作节点可以加入集群。

为了自动化此过程,authconfig.tf文件定义了authconfig YAML 文件的内容,您将使用它来注册和验证与 EKS 控制平面的工作节点。

值得一提的是,authconfig 可以通过 kubectl 单独应用于集群。然而,我建议使用 Terraform 立即在 EKS 配置后注册节点,然后你可以在 Kubernetes 配置管理的一部分中再次应用它,向 authconfig 添加更多用户和组。

authconfig.tf 文件定义如下:

locals {
  authconfig = <<AUTHCONFIG
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: "${aws_iam_role.workers.arn}"
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
AUTHCONFIG
}

第四章使用 Ansible 管理集群配置 中,我们将学习如何扩展 aws-auth 来认证其他用户与集群的连接。

输出值

outputs.tf 文件定义了来自 Workers 模块的输出值,例如工作节点的实例配置文件 ARN、IAM 角色 ARN 和其他输出。请查看 outputs.tf 的完整源代码,链接地址为 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter03/terraform/modules/eks-workers/outputs.tf

在这一部分,你学会了为集群工作节点开发一个 Terraform 模块。你将与其他模块一起使用它来组合你的集群基础设施。在下一部分,你将学习如何开发一个 Terraform 模块,将 EKS 和工作节点封装在一个模块中,代表整个 Kubernetes 集群。

开发 Kubernetes 集群的 Terraform 模块

terraform/modules 目录下,创建一个子目录并命名为 cluster。该目录将包含以下 Terraform 代码文件:

  • config.tf

  • terraform.tfvars

  • variables.tf

  • main.tf

  • outputs.tf

这个 cluster 模块是 EKS 模块和工作节点模块的包装器。你会注意到,进出这个模块的输入和输出是 EKS 模块和工作节点模块的组合。

输入变量

variables.tf 文件定义了此模块所需的输入变量。这些输入是 EKS 模块和工作节点模块的组合。请查看完整的变量列表源代码,链接地址为 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter03/terraform/modules/cluster/variables.tf

EKS 控制平面

eks-cp.tf 文件定义了一个 EKS 模块的实例。它的定义如下:

module "eks" {
  source            = "../eks-cp"
  vpc_id            = var.vpc_id
  private_subnets   = var.private_subnets
  public_subnets    = var.public_subnets
  cluster_full_name = var.cluster_full_name
  cluster_version   = var.cluster_version
  common_tags       = var.common_tags
}

前面的代码块通过创建 EKS 模块的实例并传递所需的输入,来创建 EKS 控制平面。

EKS 工作节点

workers.tf 文件定义了 workers 模块的一个实例:

module "workers" {
  source                 = "../eks-workers"
  vpc_id                 = var.vpc_id
  private_subnet_ids     = var.private_subnets
  cluster_full_name      = var.cluster_full_name
  cluster_endpoint       = module.eks.endpoint
  cluster_ca             = module.eks.ca
  cluster_security_group = module.eks.security_group
  workers_ami_id         = var.workers_ami_id
  workers_instance_type  = var.workers_instance_type
  workers_number_max     = var.workers_number_max
  workers_number_min     = var.workers_number_min
  workers_storage_size   = var.workers_storage_size
  common_tags            = var.common_tags
}

前面的代码块通过创建 workers 模块的实例并传递所需的输入,来创建集群工作节点。这两个代码文件共同组成了完整的 Kubernetes 集群。

输出值

outputs.tf 文件包含来自 cluster 模块的输出值,例如集群的全名、集群端点、authconfig 等。请在此处查看完整源代码。

在本节中,你学习了开发一个 Terraform 模块,该模块将 EKS 和工作节点包装成一个单独的模块,用于配置整个 Kubernetes 集群。在下一节中,你将使用之前的模块来开发你的第一个集群——Packt 集群。

将所有模块组合在一起

现在是时候将所有模块结合起来,创建你的第一个集群组 packtclusters 和第一个集群 prod1

在根 terraform 目录下,创建一个子目录并命名为 packtclusters。然后,在该目录下创建以下 Terraform 代码文件:

  • config.tf

  • terraform.tfvars

  • variables.tf

  • main.tf

  • outputs.tf

在接下来的子章节中,你将创建上面列表中的代码文件,并了解它们的所有细节。

配置

config.tf 文件包含 Terraform 共享状态配置和 AWS 提供程序定义。此文件类似于你在 开发集群 VPC 部分创建的 config.tf 文件。请在此处查看完整源代码。

环境变量

terraform.tfvars 文件定义了传递给 cluster 模块的输入值。其中一些值是 VPC 模块的输出值。要获取这些输出,您需要执行以下命令:

$ cd Chapter03/terraform/packtclusters-vpc
$ terraform output

然后,复制以下输出值:

  • VPC ID

  • 私有子网 ID

  • 公共子网 ID

然后,将这些值粘贴到 terraform.tfvars 文件的相应位置。

terraform.tfvars 文件定义如下:

aws_region = "us-east-1"
private_subnet_ids = [
  "subnet-xxxxxxxx",
  "subnet-xxxxxxxx",
  "subnet-xxxxxxxx",
]
public_subnet_ids = [
  "subnet-xxxxxxxx",
  "subnet-xxxxxxxx",
  "subnet-xxxxxxxx",
] 
vpc_id                = "vpc-xxxxxxxxxx"
clusters_name_prefix  = "packtclusters"
cluster_version       = "1.16"
workers_instance_type = "t3.medium"
workers_number_min    = 1
workers_number_max    = 3
workers_storage_size  = 10

前述的一些值可以根据你的基础架构要求进行调整,特别是实例类型和工作节点实例数的最小/最大限制。

为了教学目的,你可以使用前面代码块中的现有值。然而,当你决定将集群迁移到生产环境时,请参考 第二章生产级 Kubernetes 基础架构设计 中的工作节点规格部分。

输入变量

variables.tf文件定义了 Terraform 在创建packtclusters-prod1集群时将使用的输入。你可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter03/terraform/packtclusters/variables.tf查看完整的源代码。

集群的主要资源

main.tf文件定义了集群模块。它接受配置 EKS 和工作节点所需的输入变量。

main.tf文件定义如下:

module "packtcluster" {
  source                = "../modules/cluster"
  vpc_id                = var.vpc_id
  public_subnets        = var.public_subnet_ids
  private_subnets       = var.private_subnet_ids
  cluster_full_name     = "${var.clusters_name_prefix}-${terraform.workspace}"
  cluster_version       = var.cluster_version
  workers_instance_type = var.workers_instance_type
  workers_ami_id        = data.aws_ssm_parameter.workers_ami_id.value
  workers_number_min    = var.workers_number_min
  workers_number_max    = var.workers_number_max
  workers_storage_size  = var.workers_storage_size
  common_tags           = local.common_tags
  aws_region            = var.aws_region
}

在前面的代码块中,cluster_full_name输入是通过连接cluster_name_prefix(即packtclusters)和 Terraform 工作区名称prod1来构造的。通过这种方式,你可以在一个集群组下创建多个集群,例如packtclusters。你所需要做的只是创建一个新的 Terraform 工作区,并执行你的terraform plan

输出值

outputs.tf文件定义了来自packtclusters的输出,主要是authconfig,用于将工作节点与控制平面进行身份验证。你可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter03/terraform/packtclusters/outputs.tf查看完整的源代码。

完成此部分后,你将拥有一个完整的 Terraform 代码库,能够创建完整的 Kubernetes 集群。在下一部分中,你将学习使用该代码库来配置第一个生产集群的 Terraform 命令。

集群基础设施的配置

在完成前几节中的集群 Terraform 模块开发后,你现在可以在 AWS 帐户中配置并创建你的第一个 Kubernetes 集群:

  1. 初始化 Terraform 状态:

    $ cd Chapter03/terraform/packtclusters
    $ terraform init
    
  2. 为第一个集群创建一个新的 Terraform 工作区,并命名为prod1

    $ terraform workspace new prod1
    
  3. 执行terraform plan命令,在应用之前审查计划中的更改:

    $ terraform plan
    
  4. 这是你应该获得的terraform plan命令的输出:![图 3.7 – Terraform 计划命令输出]

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_03_007.jpg)

    图 3.7 – Terraform 计划命令输出

  5. 执行terraform apply命令。在提示批准计划执行时,输入yes

    $ terraform apply
    
  6. terraform apply命令成功完成后,你将看到以下输出。这意味着 Terraform 已成功创建了 22 个资源:![图 3.8 – Terraform apply 命令输出]

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_03_008.jpg)

    图 3.8 – Terraform apply 命令输出

  7. 获取集群的kubeconfig文件:

    $ aws eks --region $(terraform output aws_region) update-kubeconfig --name $(terraform output cluster_full_name)
    Added new context arn:aws:eks:us-east-1:698782116220:cluster/packtclusters-prod1 to ~/.kube/config
    
  8. 应用authconfig以验证工作节点与 EKS 控制平面的身份:

    $ terraform output authconfig | kubectl -n kube-system create -f –
    configmap/aws-auth created
    
  9. 确保集群的工作节点已启动并处于准备就绪状态:

    $ kubectl get nodes
    NAME			   STATUS	ROLES	 AGE	 VERSION
    ip-10-40-98-176.ec2.internal	Ready	<none>	 90s	v1.15.10-eks-bac369
    

完成前面的操作后,您已成功启动并运行 Kubernetes 集群,但它仍未准备好部署生产工作负载。在接下来的章节中,您将向集群部署更多服务,并优化其配置,使其能够运行生产工作负载。

清理并销毁基础设施资源

完成本章的动手操作后,您可以按照本节中的说明销毁 Kubernetes 集群及其 AWS 资源。

您将按创建的反向顺序销毁资源。首先销毁 Kubernetes 集群资源,然后销毁 VPC 资源,最后销毁共享状态资源。

销毁集群资源

按照以下 Terraform 命令销毁您在本章前面部分创建的所有packtclusters资源:

  1. 初始化 Terraform 状态:

    $ cd Chapter03/terraform/packtclusters
    $ terraform init
    
  2. 执行terraform destroy命令。当系统提示确认销毁时,输入yes

    $ terraform destroy
    
  3. terraform destroy命令成功完成后,您将看到以下输出。这意味着 Terraform 已成功销毁了集群中的 22 个资源:

图 3.9 – terraform destroy 命令输出

图 3.9 – terraform destroy 命令输出

根据前面的操作,packtclusters-prod1已完全销毁。在下一小节中,您将销毁 VPC 资源。

销毁 VPC 资源

按照以下 Terraform 命令销毁您在本章前面部分创建的所有packtclusters-vpc资源:

  1. 初始化 Terraform 状态:

    $ cd Chapter03/terraform/packtclusters-vpc
    $ terraform init
    
  2. 执行terraform destroy命令。当系统提示确认销毁时,输入yes

    $ terraform destroy
    
  3. terraform destroy命令成功完成后,您将看到以下输出。这意味着 Terraform 已成功销毁了 28 个网络资源:

图 3.10 – terraform destroy 命令输出

图 3.10 – terraform destroy 命令输出

根据前面的操作,packtclusters-vpc已完全销毁。在下一小节中,您将销毁共享状态资源。

销毁共享状态资源

通常,您不需要删除共享状态文件。然而,出于教学目的,您可以按照以下说明销毁这些资源。

  1. 由于共享状态 S3 桶启用了销毁防护和版本控制,您应该首先清空并销毁 Terraform 共享状态 S3 桶:

    $ aws s3 rm s3://packtclusters-terraform-state --recursive
    $ aws s3 rm s3://packtclusters-vpc-terraform-state --recursive
    $ aws s3 rb s3://packtclusters-terraform-state --force
    $ aws s3 rb s3://packtclusters-vpc-terraform-state --force
    
  2. 初始化 Terraform 状态以销毁共享状态 DynamoDB 表:

    $ cd Chapter03/terraform/shared-state
    $ terraform init
    
  3. 执行terraform destroy命令。当系统提示确认销毁时,输入yes

    $ terraform destroy
    
  4. terraform destroy命令成功完成后,您将看到以下输出。此时,Terraform 已成功销毁了两个 DynamoDB 表:

图 3.11 – terraform destroy 命令输出

图 3.11 – terraform destroy 命令输出

到目前为止,您已经成功销毁了您的 Kubernetes 集群以及 AWS 账户中的所有 AWS 资源。

我建议您练习这些指令,重复进行集群的提供和销毁,并通过添加新的 Terraform 工作区(如 prod2 和 prod3)创建多个集群。

总结

在本章中,您学习了如何使用 Terraform 和 AWS 开发 Kubernetes 集群的基础设施代码。您通过实际步骤实现了这段代码。我们从创建网络组件开始,接着是集群组件,使用了 AWS VPC、EKS、自动扩展组和其他 AWS 服务。

本章向您介绍了 Terraform 的实用开发及其在生产基础设施提供中的应用。它展示了如何遵循声明式基础设施即代码(IaC)的最佳实践,并将 IaC 拆分为模块并将它们结合起来,以创建 Kubernetes 集群的最佳实践。

所有这些为接下来的章节奠定了基础,我们将在这些章节中基于本章介绍的知识,进一步推动 Kubernetes 集群进入生产就绪的下一阶段。

在下一章中,您将详细了解 Kubernetes 集群配置管理。您将开发一个动态模板解决方案,可以将其应用于集群级别的配置,并且您将学习如何使您的解决方案能够扩展到多个集群,而无需引入额外的运营开销和复杂性。

进一步阅读

若要了解本章所涵盖的主题,您可以参考以下书籍:

第四章:第四章:使用 Ansible 管理集群配置

第三章使用 AWS 和 Terraform 部署 Kubernetes 集群中,你学习了如何使用 Terraform 和 AWS 创建 Kubernetes 基础设施,并且学会了如何开发基础设施即代码,并为你的第一个生产类集群进行了部署。

这只是构建可操作和生产就绪的 Kubernetes 集群的第一步。到目前为止,你应该已经有一个运行中的集群,并且通过 Terraform 基础设施模块来部署其他类似的集群。

这些集群仍然是普通的,它们没有经过配置或优化以运行生产工作负载。为了让这些集群完全可用,我们只需要部署和配置所需的 Kubernetes 服务。

在本章中,你将设计并开发一个配置管理解决方案,用于管理 Kubernetes 集群及其支持服务的配置。该解决方案是自动化和可扩展的,且需要最小的维护和操作工作量。

本章将涵盖以下主题:

  • 了解 Kubernetes 配置管理的挑战

  • 为 Kubernetes 设计配置管理解决方案

  • 使用 Ansible 开发配置管理解决方案

  • 应用该解决方案配置 Kubernetes 集群

技术要求

除了你在第三章使用 AWS 和 Terraform 部署 Kubernetes 集群中安装的工具外,你还需要安装以下工具:

  • python3

  • pip3

  • virtualenv

我将在下一节中详细讲解这些工具的安装和配置。如果你已经知道如何操作,可以直接开始安装它们。

你需要根据第三章使用 AWS 和 Terraform 部署 Kubernetes 集群中的说明,拥有一个运行中的 Kubernetes 集群。

本章的代码位于 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter04

查看以下链接,观看《代码实战》视频:

bit.ly/3cGtqjx

安装所需工具

python3pip3virtualenv是执行我们将在本章中开发的 Ansible 配置剧本的先决条件。如果你的系统上没有这些工具,你可以按照以下说明安装:

  • 执行以下命令在 Ubuntu Linux 上安装python3pip3virtualenv

    $ sudo apt-get update
    $ sudo apt-get install python3
    $ sudo apt-get install python3-pip
    $ sudo pip3 install virtualenv
    
  • 执行以下命令在 Amazon Linux 2 上安装python3pip3virtualenv

    $ sudo yum update
    $ sudo yum install python3
    $ sudo python3 -m pip install --upgrade pip
    $ sudo python3 -m pip install virtualenv
    
  • 执行以下命令在 macOS 上安装python3pip3virtualenv

    $ brew install python3
    $ curl -O https://bootstrap.pypa.io/get-pip.py
    $ sudo python3 get-pip.py
    $ sudo -H pip3 install virtualenv
    
  • 执行以下命令以在 Windows 上安装 python3pip3virtualenv

    C:\> choco install python3
    C:\> pip install virtualenv 
    

通过安装python3pip3virtualenv,你将能够在 Kubernetes 集群上执行 Ansible playbook。你将在本章后续部分学习如何操作,但首先,我们需要了解 Kubernetes 配置管理解决方案的设计细节。

实现原则

第一章《Kubernetes 基础设施与生产就绪性介绍》中,你了解了本书中将遵循的基础设施设计原则。我想通过强调那些影响本章配置管理解决方案和技术决策的关键原则来开始这一章:

  • 一切皆代码:在本章中,我们将坚持将基础设施的一切都作为代码——集群配置也不例外。你将通过使用 Ansible 创建一个 Kubernetes 集群的配置管理解决方案来实现这一目标。

  • 自动化:在上一章中,我们使用了 Terraform 工具来自动化基础设施的配置。我们围绕 Terraform 设计了解决方案,可以在无需扩展基础设施团队的情况下,支持越来越多的集群。在这里,你将创建一个类似的解决方案来管理 Kubernetes 配置,同时保持其自动化、可扩展性,并易于操作和维护。

  • 简洁性:Ansible 在许多方面实现了这一原则,因为它容易学习和使用。与其他配置管理工具相比,它的语法简单。它使用 YAML,写它时不需要学习编程语言。此外,它是无代理的,这意味着你不需要服务器来运行它,可以直接从你的计算机运行 Ansible。它还是模块化的,这使得关注点分离和代码重用成为可能,这与 Terraform 相似。因此,它们可以轻松地共存,简化基础设施的自动化。

Kubernetes 配置管理

Kubernetes 的美在于它的每个部分都被抽象为一个对象,可以通过其 API 服务器使用 YAML 或 JSON 进行声明式管理和配置。这使得 Kubernetes 配置更容易作为代码进行管理。然而,当你有多个集群,且这些集群运行着数百个附加组件和服务时,管理这些配置依然是一个挑战。

假设你正在管理一个公司使用 Kubernetes 的基础设施,并且你有多个集群用于开发、测试和生产。再加上 Kubernetes 服务层运行的集群附加组件,如下图所示:

图 4.1 – Kubernetes 基础设施层次

图 4.1 – Kubernetes 基础设施层次

这意味着你可以拥有N个集群,并且随着附加组件数量的增加以及不同环境类型(如开发、QA 和生产)的变化,配置也会越来越复杂。如果我们将这些因素结合起来,就会面临一个复杂且冗余的配置管理问题。

管理集群配置的推荐方式是通过配置即代码CaC)。我们将把这些服务和附加组件部署到集群中,并将它们的配置清单添加到源代码控制中。通过采用这种模式,你将能够以无缝和自动化的方式重新部署相同的配置到集群中。当你从单个集群开始时,这种解决方案看起来很简单,但当需要为多个集群提供不同配置值时,它将变得难以维护和扩展。

这促使我们提出了一种增强的解决方案,即配置模板化。假设你有一组集群用于产品 X,这些集群有不同的配置,比如不同的用户认证与授权、命名空间、资源配额等。

这个解决方案使用了 Ansible 模板和 Jinja2。你只需编写一次 Kubernetes 清单的模板,然后 Ansible 会在这些模板中替换变量,为每个目标集群生成相应的清单。这个解决方案具有可扩展性且易于维护,并且满足我们在第一章《Kubernetes 基础设施与生产就绪介绍》中提出的基础设施设计原则。

Kubernetes 配置管理工作流程

在考虑了前述的模板化解决方案后,我们的 Kubernetes 配置管理工作流程如下所示:

  1. 为你想要配置和部署的 Kubernetes 集群服务创建 Ansible Jinja2 模板。

  2. 定义变量的值,并根据环境和集群组对其进行分类。

  3. 使用 Terraform 进行集群的配置。

  4. 将 Terraform 输出传递给 Ansible。

  5. 将 Ansible 模板中的变量替换为相应的值。

  6. 使用 Ansible 将 Kubernetes 清单应用到目标集群。

在接下来的章节中,我们将使用 Ansible 和 Jinja2 模板来实现这个工作流程,然后通过一个基本示例来学习如何使用它。

使用 Ansible 进行配置管理

在本章中,我们将使用 Ansible 作为配置管理工具,并围绕它构建我们的 Kubernetes 配置管理解决方案。在本节中,我们将简要讨论选择 Ansible 的理由以及一些 Ansible 的关键概念。如果你想深入了解 Ansible,可以访问其官方指南:www.ansible.com/resources/get-started

为什么选择 Ansible?

当涉及到 Kubernetes 配置模板化时,我们有经过实践验证的工具。最著名的工具是 Ansible 和 Helm,它们各有优缺点。但我这里并不是要做它们的全面对比。我做出选择的依据是曾经在生产环境中使用过这两个工具,以及我们的具体用例。当涉及到纯粹的配置管理和模板化时,Ansible 仍然是最强的竞争者。虽然 Helm 支持模板化,但它更像是一个 Kubernetes 包管理工具,而不是一个完整的配置管理工具。这就是我们决定使用 Ansible 来开发 Kubernetes 基础设施配置管理解决方案的原因。

什么是 Ansible?

Ansible 是一款自动化和配置管理(CM)工具。它可以配置系统,部署应用和容器,及配置云资源。它能够协调高级任务,如持续部署和滚动更新。

在本书中,我们不会深入探讨 Ansible 的特性和使用案例。我们认为有很多优秀的书籍专门讨论这个主题;我们的主要关注点是如何以简单且高效的方式使用 Ansible 来解决 Kubernetes 的配置管理(CM)问题。

Ansible 关键概念

本书中我们将实施并使用的配置管理解决方案是基于 Ansible 的关键概念构建的。我不会深入讲解这些概念,而是简要说明它们,并突出我们将如何在配置管理框架中使用它们。

  • 清单(Inventory):Ansible 用来将相似的主机分组。通过定义包含主机地址的清单文件来实现这一点。

  • 模块(Modules):这是 Ansible 如何抽象并将特定任务分组,以便在你的主机清单上重复使用;模块甚至可以公开,并被其他 Ansible 用户使用。在我们的解决方案中,我们将使用一个现成的 Kubernetes 模块来对集群执行配置清单。

  • 任务(Tasks):这是我们指示 Ansible 执行步骤的地方;它可能是安装一款软件或配置一个完整的系统。在我们的解决方案中,我们将为每个 Kubernetes 组件和插件创建一个单独的任务。

  • 剧本(Playbooks):这是 Ansible 的构建块。它们用来将所有内容集合在一起,并提供一系列指令,涉及到其他 Ansible 块,如任务、变量和模块。然后它们指示 Ansible 如何配置目标系统,以达到期望的状态。在我们的解决方案中,我们将使用一个剧本来保存所有集群所需的组件和插件的配置任务,并且我们还会使用变量和选择器来允许集群管理员切换特定插件的启用/禁用。

  • 变量:我们将使用变量来存储每个集群附加组件配置所需的值,并将这些变量分组,代表不同的集群和环境。

  • 模板:Ansible 使用 Jinja2 模板来启用动态表达式,并使用变量。这使得 Ansible 可以在执行时根据这些模板生成新的配置文件。在我们的解决方案中,我们将 Kubernetes 清单定义为 Ansible Jinja2 模板,在配置执行时,Ansible 将能够根据提供的或预定义的变量为每个集群生成正确的 Kubernetes 清单。

之前的 Ansible 概念对于理解 Ansible 的工作原理至关重要。在接下来的章节中,我们将利用它们开发 CM 解决方案。你将随着章节的推进,学习每个概念及其使用方法。

配置集群

现在我们将把前一节中设计的解决方案付诸实践。我们将从开发 Ansible 框架骨架开始,骨架将由以下部分组成:

  • group_vars:该目录包含带有变量默认值的清单配置文件,除非集群在其自己的库存中定义了私有变量。

  • inventories:该目录包含带有变量值的配置文件,这些配置文件特定于每个集群或集群组,意味着此处定义的变量会覆盖 groups_vars 目录下定义的默认变量。

  • tasks:在这个目录中,我们为每个集群服务和附加组件定义一个独立的任务,并且任务定义文件在各个任务之间是标准化的,因为我们将使用 Ansible 的 k8s 模块并将 YAML 模板传递给它以在目标集群上进行部署。

  • templates:该目录包含我们需要管理的每个 Kubernetes 对象的 Kubernetes 清单 YAML 文件和配置文件,这些模板文件将采用 Jinja2 表达式格式书写所需的变量。

  • cluster.yaml:这是传递给 Ansible 以在目标集群上执行的主要 playbook。它包含我们需要调用的所有任务,以配置集群对象和附加组件。该 playbook 还为每个任务添加了标签,这使得集群维护人员可以在需要时为每个目标集群开启/关闭特定任务。

在为 Kubernetes 集群配置管理创建 Ansible 框架骨架后,我们将能够扩展它以处理更多的集群服务和部署。开发工作流如下:

  1. 为你想要部署的集群附加组件编写 Kubernetes 清单 YAML 格式的文件,然后将它们部署到测试集群以确保正确性。

  2. 将 Kubernetes 清单从 YAML 转换为 Jinja2 模板。

  3. 创建一个任务文件来调用这些模板,并将该文件添加到 Ansible tasks 目录下。

  4. 创建变量值:

    • 对于默认的变量值,在 group_vars 目录下,添加你在模板中创建的变量值,并将其写入适当的 YAML 文件。

    • 对于集群特定的变量,在 inventories 目录下,创建一个新目录,命名为你要目标的集群或集群组的名称,然后创建它自己的 group_vars 目录,并在该目录下创建一个 YAML 文件,用于包含变量值映射。

  5. 更新 playbook 文件,并添加步骤以调用目标任务。然后,将适当的标签和属性关联到该任务。

在实际操作中,我们将配置 aws-auth 并创建一个 Kubernetes 命名空间,演示该 Ansible 解决方案是如何工作的。在接下来的章节中,我们将使用这个解决方案在 Kubernetes 上部署更多的服务和插件。

ansible 目录的结构

ansible 目录是你的基础设施仓库中所有 Ansible 源代码所在的位置。作为最佳实践,我建议你拥有一个专门的基础设施源代码仓库,里面包含所有基础设施代码和你 Kubernetes 集群及其他基础设施的配置。以下是我们将在本章开发的 Ansible 配置的提议目录结构:

![图 4.2 – Ansible 目录结构]

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_04_002.jpg)

图 4.2 – Ansible 目录结构

你将通过详细的学习和实践,了解如何开发此解决方案,以及在 ansible 目录下的所有配置代码。

创建 Ansible 模板

在本节中,你将创建两个模板,学习如何将 Kubernetes 清单转换为 Ansible Jinja2 格式。

第二个模板是 Kubernetes 命名空间,你将使用它来创建新的命名空间。

创建 aws-auth 模板

第一个模板是 aws-auth ConfigMap,你将使用它来定义 AWS IAM 用户和角色,并将它们认证到集群。你将在第六章中详细了解 aws-auth 以及如何在 有效保护 Kubernetes 中使用它进行集群访问。

你将为 aws-auth ConfigMap 创建一个 Jinja2 模板。但是,在我们开始模板化之前,先看一下默认的 aws-auth ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: <ARN of instance role (not instance profile)>
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes

前面的代码块创建了一个aws-auth ConfigMap,并为工作节点 EC2 创建了一个角色。但是,如果我们需要添加更多角色和用户怎么办?如果我们需要将相同的 ConfigMap 用于不同的集群,并且每个集群都需要不同的工作节点 aws-auth ConfigMap,该怎么办?

下一个 aws-auth 模板的代码块定义了一组特定的用户和角色,这些用户和角色可以访问集群。在代码的第一部分,你需要定义 Kubernetes 的 apiVersion,对象类型为 ConfigMap,以及元数据:

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth

在代码的第二部分,你定义了 ConfigMap data部分,其中包含了带有 Jinja2 变量的for循环,这些变量可以在执行时由 Ansible 替换。你会注意到,我们使用了for循环,以便可以添加多个用户:

data:
  mapUsers: |
{% for user in map_users.system_masters %}
    - userarn: "{{ user.arn }}"
      username: "{{ user.name }}"
      groups:
        - system:masters
{% endfor %}

在代码的第二部分,你定义了另一个 ConfigMap data部分,其中包括 IAM 角色。首先,不是将每个用户的数据(姓名、ARN 和 Kubernetes 组)逐个添加,而是将它们定义在一个 Jinja2 的for循环中,Jinja2 变量可以在执行时由 Ansible 替换。你会注意到,我们使用了for循环,以便可以添加多个角色:

  mapRoles: |
{% for role in map_roles.workers_roles %}
    - rolearn: "{{ role }}"
      username: {% raw -%} "system:node:{{ '{{' }}EC2PrivateDNSName{{ '}}' }}" {%- endraw %}
      groups:
        - system:bootstrappers
        - system:nodes
{% endfor %}
{% for role in map_roles.system_masters %}
    - rolearn: "{{ role }}"
      username: {% raw -%} "admin:{{ '{{' }}SessionName{{ '}}' }}" {%- endraw %}
      groups:
        - system:masters
{% endfor %}

之前的模板可以验证 IAM 用户和角色对任何集群的身份,并且你甚至可以根据需要进一步扩展,添加不同的组类型。但原始概念保持不变,你有一个针对任何集群、任何用户和角色都能使用的aws-auth ConfigMap 的单一模板。

创建 Kubernetes 命名空间模板

下一个代码块是一个 Jinja2 模板,用于生成 Kubernetes 命名空间清单的 YAML 文件。此模板定义了基本的命名空间配置,如名称、标签和注释。

该模板可以创建多个命名空间,因为它从目标集群的 Ansible 变量中读取命名空间列表,并为每一个命名空间生成 Kubernetes 清单 YAML 文件。

{% for namespace in namespaces_list %}
---
apiVersion: v1
kind: Namespace
metadata:
  name: {{ namespace.name }}
  labels:
    name: {{ namespace.name }}
    owner: {{ namespace.owner }}    
{% endfor %}

之前的模板是一个示例,展示了你如何为 Kubernetes 对象创建自己的模板。我建议在编写这些模板时,访问 Ansible Jinja2 的官方文档,以便获得更多关于代码块及其使用方法的灵感:docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html

创建 Ansible 变量

正如你在本章前面学到的,Ansible 的group_vars将包含你想要应用于所有集群的全局配置变量,除非你希望为特定集群指定不同的值。在这一部分,你将为aws-auth ConfigMap 中的管理员用户定义默认变量,并定义一个新的命名空间。

定义 aws-auth 变量

以下代码片段定义了集群配置的默认变量,当集群没有自己的私有变量时,将使用这些默认变量。第一个变量是worker_iam_role_arn。Ansible 将从 Terraform 的输出中获取worker_iam_role_arn的值。第二个变量是集群的管理员。你还添加了被称为admin的 ARN 或 IAM 用户:

map_roles:
  workers_roles:
    - "{{ worker_iam_role_arn }}"
  system_masters: []
map_users:
  system_masters:
    - arn: "<ARN of the admin user>"
      name: "admin"

你可以扩展之前的变量,根据需要为集群添加更多角色和用户。你还将在第六章有效保护 Kubernetes》中学习到 Kubernetes 的基于角色的访问控制RBAC)和访问管理最佳实践。

重要说明

在 Jinja2 模板中,你需要在双括号 {{ }} 中定义变量。请参考 Ansible 模板文档:docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html

配置默认命名空间

你将把一个命名空间添加到 namespaces_list 变量中。不过,你可以根据需要添加更多的命名空间。这是一个展示如何与 Ansible 配合使用命名空间配置的示例:

namespaces_list:
- name: default-namespace
  owner: admin

在本节中,你应该已经学会了如何为你的集群创建默认配置变量。这是一个简单的配置机制,但非常强大和高效。

创建 Ansible 清单

不是所有集群都是相同的。在上一节中,你学习了如何为你的配置设置默认变量。但是,如果你需要为某个集群设置不同的值呢?Ansible 清单就是答案。在这一节中,你将创建清单来定义本地集群变量,以覆盖默认变量。

创建 Ansible 清单

Ansible 配置主机(服务器/虚拟机)的方法非常简单。通常,存在一个主机或一组主机,你有配置任务要应用于这些主机。但是我们的解决方案是一个不同的用例,因为我们将使用相同的概念,但不是针对任何远程主机。因为实际上,我们并不配置主机——我们配置的是 Kubernetes 集群。Ansible 只需要与 Kubernetes API 服务器进行通信。

你需要做的就是将 Ansible 的 hosts 设置为目标 localhost。然后,localhost 将使用 kubeconfig 中定义的 kube-server API 端点来应用预定的配置:

[all]
localhost
[override]
localhost

如你在前面的代码块中所看到的,Ansible 的目标主机只定义了 localhost 值。这个 hosts 文件应该为 Ansible 管理的每个清单都存在。

覆盖 aws-auth 变量

要覆盖在 group_vars 中定义的 aws-auth 默认变量,你需要在 packtclusters 清单下重新创建 aws-auth 模板文件,并为新变量设置相应值。以下代码块展示了如何覆盖 aws-auth。定义了两个 IAM 角色:第一个角色为工作节点,第二个角色为集群管理员角色。代码的第二部分定义了一个不同于默认用户的用户:

map_roles:
  workers_roles: 
    - "{{ worker_iam_role_arn }}"
  system_masters:
    - "<ARN of the admin-role user>"
map_users:
  system_masters:
    - arn: "arn:aws:iam::AWS_ACCOUNT_NO:user/packtclusters-admin"
      name: "packtclusters-admin"

之前的配置模板将替换 packtclusters 的默认模板。你可以对任何其他模板执行相同操作。

覆盖命名空间变量

要覆盖 group_vars 中定义的 namespaces 默认变量,你需要在 packtclusters 库存中重新创建 namespaces 模板文件,并填入新变量的值。在下面的代码块中,有一个新变量,它将用名为 packtclusters-namespace 的新值覆盖 default-namespace。因此,当你应用此配置时,packtclusters 将使用新的命名空间,而不是默认的命名空间:

namespaces_list:
- name: packtsclusters-namespace
  owner: packtclusters-admin

在这一部分中,你已经学习了如何覆盖 Ansible 的默认变量,以便根据集群使用不同的配置值。

创建 Ansible 任务

创建 Ansible 模板后的第二步是创建 Ansible 任务。在这一部分中,你将学习如何创建 Ansible 任务来部署你的配置模板。

这些任务将使用 Ansible 的 k8s 模块。该模块接受模板化的 Kubernetes YAML 文件,然后指示 Ansible 将这些任务应用于目标集群。Ansible 可以通过当前上下文中的 kubeconfig 文件识别目标集群。

重要说明

你可以通过官方文档了解更多关于 Ansible 的 k8s 模块:docs.ansible.com/ansible/latest/user_guide/modules_intro.html

创建 aws-auth 任务

以下任务指导 Ansible 如何生成并将 aws-auth ConfigMap 应用到集群中。它以模板文件的路径作为输入,并将其应用于目标集群。

在下面的代码块中,你定义了任务规格,包含 namekubeconfig 路径、state 以及是否强制将配置应用到集群等属性。然后,任务定义了要加载哪个 Jinja2 模板,并将其变量替换为来自 group_varsinventory 目录的值。

如果有多个 Jinja2 模板需要通过 k8s 模块应用,你会注意到有一个 loop 指令。其他重要参数包括 retries,它告诉 Ansible 任务成功之前需要重试的次数,以及 delay,它告诉 Ansible 每次重试之间的时间间隔(单位:秒):

# ansible/tasks/auth/aws-auth.yaml
- name: deploy aws auth ConfigMap
  k8s:
    definition: "{{ item }}"
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: "{{ k8s_manifests_state }}"
    force: "{{ k8s_force }}"
  loop: 
    - "{{ lookup('template', k8s_manifests_base_dir + 'auth/aws-auth.yaml') | from_yaml_all | list }}"
  register: k8s_result
  until: k8s_result is success
  retries: 3
  delay: 2
  no_log: "{{ k8s_no_log }}"  

上述 aws-auth 任务的代码将在稍后本章中学习到的 Ansible 剧本中调用。

创建命名空间任务

以下 Ansible 任务文件用于创建集群的命名空间。它接收命名空间对象模板文件的路径,并将其应用到目标集群。

namespaces 任务的代码结构与之前的 aws-auth 任务非常相似,唯一不同的是它有一个不同的名称,并且它读取一个不同的 Jinja2 模板文件 namespaces.yaml

# ansible/tasks/namespaces.yaml
- name: create cluster namespaces
  k8s:
    definition: "{{ item }}"
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: "{{ k8s_manifests_state }}"
    force: "{{ k8s_force }}"
  loop: "{{ lookup('template', k8s_manifests_base_dir + 'namespaces/namespaces.yaml') | from_yaml_all | list }}"
  register: k8s_result
  until: k8s_result is success
  retries: 3
  delay: 2
  no_log: "{{ k8s_no_log }}"

上述 namespaces 任务的代码将在稍后本章中学习到的 Ansible 剧本中调用。

创建集群的剧本

Ansible playbook 是一个 Ansible 文件,你将在其中按顺序放置所有任务,指定 Ansible 执行它们的顺序。以下集群 playbook 是一个简单的标准 Ansible playbook,包含三个部分:第一部分是定义目标主机,第二部分是定义在执行过程中任务使用的任何变量,第三部分是 Ansible 将要执行的任务列表。

以下代码块定义了主机和连接类型。在我们的解决方案中,正如之前所述,我们将使用localhost作为目标主机:

# ansible/cluster.yaml
---
- name: deploy k8s add-ons
  hosts: localhost
  connection: local
  gather_facts: no

以下代码块定义了执行任务过程中所需的变量。最值得注意的是指向kubeconfig文件的物理路径和存放 Kubernetes 模板的基础目录。这些变量会覆盖group_varsinventory目录中任何具有相似名称的变量:

  vars:
    Ansible_python_interpreter: "{{ Ansible_playbook_python }}"
    k8s_kubeconfig: ~/.kube/config
    k8s_manifests_base_dir: templates/
    k8s_manifests_state: present
    k8s_force: false
    k8s_no_log: false

以下代码块定义了 Ansible 针对目标集群执行的任务列表。你可以将新任务添加到此列表,并为其分配有意义的标签:

  tasks:
  - import_tasks: tasks/aws-auth.yaml
    tags: aws-auth
  - import_tasks: tasks/namespaces.yaml
    tags: namespaces

通过完成 playbook、任务及所有配置的开发,你就准备好将所有 Ansible 部分整合在一起,应用 playbook 并让 Ansible 配置你的集群。在下一部分中,你将使用前一章中创建的packtclusters-prod1集群来应用 Ansible playbook。

应用集群的 Ansible playbook

接下来的指令将部署 Ansible playbook,配置你的集群以达到预期的配置:

  1. 初始化 Terraform 状态并通过以下命令选择工作区:

    $ cd terraform/packtclusters
    $ terraform init
    $ terraform workspace select prod1
    
  2. 获取并配置目标集群的 localhost kubeconfig

    $ aws eks --region $(terraform output aws_region) update-kubeconfig --name $(terraform output cluster_full_name)
    
  3. 使用 Python 的virtualenv安装并执行 Ansible:

    $ virtualenv $HOME/ansible-k8s-workspace
    $ source $HOME/ansible-k8s-workspace/bin/activate
    
  4. 安装 Ansible 及其先决模块openshiftpyyamlrequests

    $ pip install ansible==2.9 openshift pyyaml requests
    
  5. 执行 Ansible playbook:

    $ ansible-playbook -i \
    ../../ansible/inventories/packtclusters/ \
    -e "worker_iam_role_arn=$(terraform output worker_iam_role_arn)" \
    ../../ansible/cluster.yaml
    

    执行成功后,你将得到以下输出:

    图 4.3 – Ansible 执行输出

    图 4.3 – Ansible 执行输出

  6. 执行以下kubectl命令,确保集群配置已成功应用:

    packtclusters-namespace:
    

图 4.4 – 集群命名空间列表

图 4.4 – 集群命名空间列表

你按照之前的指示应用了集群的 playbook 和任务。在接下来的章节中,你将学习如何使用相同的配置管理解决方案创建其他任务,以便在集群上部署和配置服务。

销毁集群的资源

你可以按照第三章《使用 AWS 和 Terraform 部署 Kubernetes 集群》中的销毁网络和集群基础设施部分的指示,销毁 Kubernetes 集群及其相关的 AWS 资源。请确保按以下顺序销毁资源:

  1. 集群packtclusters资源

  2. 集群 VPC 资源

  3. Terraform 共享状态资源

在执行完前面的步骤后,所有集群的 AWS 资源应已成功销毁。你仍然可以登录 AWS 网页控制台,仔细检查资源销毁情况,以避免任何不必要的 AWS 费用。

总结

在本章中,你学习了 Kubernetes 配置管理的挑战,以及如何扩展你的配置管理解决方案以管理多个集群和环境。我们设计并开发了一个基于 Ansible 的解决方案,并通过实践操作示例部署了这段代码。

我们从为 Kubernetes 对象和附加组件创建 Ansible 模板开始。接着,我们开发了任务和剧本,以顺序执行 Ansible 配置,针对目标集群进行操作。

本章介绍了 Ansible 的基本概念。它向你展示了如何使用基础设施和配置作为代码、自动化和 Ansible 开发的最佳实践。

这为接下来的章节奠定了基础,在这些章节中,你将使用这个配置管理解决方案来配置和部署集群的附加组件和服务,这些附加组件对于实现生产就绪至关重要。

在下一章中,你将学习 Kubernetes 网络和连接性。将涵盖部署和配置 Kubernetes 网络插件、集群 DNS、入口控制器、网络策略和服务网格的最佳实践。

进一步阅读

你可以参考以下链接,了解本章中涉及的更多信息:

第五章:第五章:配置和增强 Kubernetes 网络服务

在上一章中,你学习了如何使用 Ansible 为 Kubernetes 开发配置管理解决方案。完成该解决方案后,你现在可以着手构建 Kubernetes 集群的上层,并在其上部署网络服务和附加组件。

本章中,我们将学习如何增强和微调核心的网络服务和附加组件,如 CoreDNS、ExternalDNS 和 Ingress Controller。我们不会深入讲解 Kubernetes 网络的基本概念。诸如 Kubernetes 网络模型、Pod 间通信、Pod 内部通信、集群服务以及基本负载均衡等主题将不在本章覆盖范围内,因为本书更关注如何将集群带到生产就绪状态,而不是深入基础知识,这些内容可以在 Kubernetes 入门书籍中学习。

本章中,我们将重点讲解如何通过重新配置已部署的服务,使集群的网络更加接近生产环境的准备状态,并且部署 Kubernetes 集群中必不可少的其他网络服务。你将学习 Kubernetes 网络最佳实践的特点,并了解如何为 Kubernetes 网络服务创建部署模板,并对其进行微调。

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

  • 引入网络生产环境准备性

  • 配置 Kube Proxy

  • 配置 Amazon CNI 插件

  • 配置 CoreDNS

  • 配置 ExternalDNS

  • 配置 NGINX Ingress Controller

  • 部署集群的网络服务

  • 销毁集群的资源

技术要求

本章的代码位于 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter05

查看以下链接,观看代码演示视频:

bit.ly/3rmhLdX

引入网络生产环境准备性

自从 Docker 和容器化时代开始以来,处理和管理容器网络一直面临着不同的挑战和复杂性。在过去的几年里,行业领导者和社区贡献者一直在努力寻找解决方案以应对这些挑战,这些努力仍在进行中。

在 Kubernetes 生态系统中,有多种容器网络模型、网络插件和工具,支持主流的用例或特定的边缘用例。你可以在 CNCF 的云原生网络地图中了解更多关于这些项目和工具的信息,链接为 landscape.cncf.io/category=cloud-native-network&format=card-mode。在本章中,我们将专注于那些对一般 Kubernetes 用例至关重要并且具备生产就绪性的服务,例如 CoreDNS、NGINX Ingress Controller 和 ExternalDNS。

在接下来的章节中,您将学习如何增强和配置预先部署的网络组件,这些组件通常是与 AWS 弹性 Kubernetes 服务 (EKS) 一起提供的,并且如何改进它们。这是在部署对网络功能、操作和可靠性至关重要的网络服务和附加组件之外的内容。

以下是我们将覆盖的网络服务和附加组件:

  • kube-proxy

  • 亚马逊 VPC K8s CNI

  • CoreDNS

  • ExternalDNS

  • NGINX Ingress Controller

对于这些组件,我们将使用 Ansible 配置管理方案进行部署和配置,具体步骤如下:

  1. 在集群的 Ansible group_vars 目录下定义配置变量,目录可在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter05/ansible/group_vars/all 找到,以及在 inventories 目录下,目录可在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter05/ansible/inventories/packtclusters/group_vars/override 找到

  2. 开发部署模板

  3. 创建 Ansible 任务

  4. 向集群剧本添加条目

如果有些代码和模板的内容没有引入新概念或改变配置,我们不会在本章中包含它们的源代码。相反,您可以在书籍的 GitHub 源代码库中查看它们,链接为 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter05

配置 Kube Proxy

kube-proxy 是一种代理服务,在集群中的每个节点上运行,负责在节点上创建、更新和删除网络规则,通常通过使用 Linux iptables。这些网络规则允许 Kubernetes 集群内部和外部的 Pod 之间进行通信。

无论是使用自管 Kubernetes 集群还是托管集群,您都需要控制传递给kube-proxy的配置选项。由于我们使用的是 EKS,kube-proxy已经随集群预部署,这使我们无法完全控制其配置,因此需要对此进行更改。

在集群生命周期中,您需要控制kube-proxy的周期性更新,并将它们包含在集群更新的管道中。此外,您还需要通过控制运行时参数来优化其性能,包括--iptables-sync-period--iptables-min-sync-period--proxy-mode

要了解其余的配置选项,请查看以下链接:kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/#options

重要说明

您可以在以下链接找到完整的源代码:github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter05/ansible/templates/kube-proxy/kube-proxy.yaml

现在,让我们为kube-proxy创建 Ansible 模板和配置:

  1. 定义配置变量并将它们添加到此路径下的group_vars目录中:ansible/group_vars/all/kube-proxy.yaml。基本配置包含镜像及其标签,这对于跟踪部署到集群中的kube-proxy版本以及控制其升级非常有用:

    kube_proxy:
      image: "602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/kube-proxy"
      tag: "v1.15.11"
    
  2. 在以下路径下创建kube-proxy DaemonSet 的部署模板:ansible/templates/kube-proxy/kube-proxy.yaml

    以下代码片段是该模板的一部分,您需要修改的代码行仅是定义imagecommand规格的地方:

    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      labels:
        eks.amazonaws.com/component: kube-proxy
        k8s-app: kube-proxy
      name: kube-proxy
      namespace: kube-system
    spec:
      selector:
        matchLabels:
          k8s-app: kube-proxy
      template:
        metadata:
          labels:
            k8s-app: kube-proxy
    

    在模板的以下部分中,您可以定义并微调kube-proxy的运行时选项,并将它们传递给容器的入口命令:

        spec:
          containers:
          - command:
            - kube-proxy
            - --v=2
            - --iptables-sync-period=20s
            - --config=/var/lib/kube-proxy-config/config        image: {{ kube_proxy.image }}:{{ kube_proxy.tag }}
    

以下是您需要考虑的kube-proxy配置选项:

  • --proxy-mode:默认情况下,kube-proxy使用iptables模式,因为它在生产环境中经过加固,且对于小型集群更快。另一方面,如果您有一个扩展集群,服务数量超过 5000,则建议使用ipvs模式,因为ipvs实现确保了更高的性能。

  • --kube-api-qps:此配置选项限制了kube-proxykube-apiserver的请求次数。该选项的默认值为5,但如果你预期你的集群会运行超过 5000 个服务,建议将其增加到10。然而,kube-proxykube-apiserver发送的 QPS 越高,kube-apiserver就会越繁忙,这可能会影响其性能。你应该根据集群的规模(运行的服务数量)和控制平面容量选择合适的 QPS 限制,以确保集群能够及时处理所有kube-proxy请求。

  • --iptables-sync-period:此选项定义了iptables规则刷新的最大时间间隔。默认情况下,设置为30s,但对于小型集群,建议将其减少到20s。集群管理员需要根据实际情况决定合适的时间间隔,并在不同优先级之间权衡。

    假设你将同步间隔减少到1s。这意味着kube-proxy需要每1s执行一次同步操作,这将增加运行kube-proxy的工作节点的负载,同时使得iptables处于繁忙状态,并阻塞其他操作。另一方面,如果你增加同步周期并减少同步频率,可能会导致一小段时间内 Pods 与iptables不同步,这可能会导致事务丢失。

还有其他选项可以处理ipvsconntrackconfigmetrics的配置。然而,修改这些配置时需要小心,如果你决定修改它们,必须先将更改部署到非生产集群中,检查性能,然后再将其推广到生产环境。

要查看完整的kube-proxy配置选项,请参阅 Kubernetes 官方文档:kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/

配置 Amazon CNI 插件

在 Kubernetes 中,容器网络接口CNI)提供了编写容器网络插件的规范和框架,以管理容器网络,包括 Pod 通信和IP 地址管理IPAM)。在本书的上下文中,我们不会深入讨论 CNI 插件及其工作原理。我们关注的是如何充分利用 CNI 插件,以及如何正确配置它。

有多种 CNI 插件经过多年的实践考验。一些插件满足一般用例的需求,例如 Calico,这是一款高度推荐的 CNI 插件,而其他 CNI 插件则倾向于解决特定的用例。

经生产测试的 CNI 插件列表包括 Calico、Cilium、Azure CNI、Contiv、Flannel、Weave Net 和 AWS CNI 等。你可以从 Kubernetes 官方文档中获取支持的 CNI 插件及其特性的完整列表:kubernetes.io/docs/concepts/cluster-administration/networking/

对于本书中我们配置的集群,我们将使用 AWS CNI 插件(amazon-vpc-cni-k8s),因为它是 EKS 的默认插件,并且它的开发旨在覆盖通用的网络使用场景,确保 Kubernetes 与 AWS 配合顺畅运行。

AWS CNI 插件已预先部署到集群中,并设置了默认配置。对于简单集群来说,这可能足够了;然而,我们需要对配置进行完全控制,因此我们决定覆盖其 DaemonSet 并将其添加到集群的 Ansible 配置中,以便更方便地管理。

在集群生命周期中,你需要控制对 amazon-vpc-cni-k8s 的定期更新,并将其包含在集群更新的管道中。此外,你还需要通过调整传递给它的配置变量来优化其性能,如 MINIMUM_IP_TARGETWARM_IP_TARGETAWS_VPC_ENI_MTU

若要了解其他 CNI 配置选项,请查看此链接:docs.aws.amazon.com/eks/latest/userguide/cni-env-vars.html

重要提示

当你将更新后的 amazon-vpc-cni-k8s DaemonSet 重新部署到集群时,CNI Pods 会被重启。更新后的 Pods 会依次推出,但这仍然会导致短暂的 CNI 插件不可用,这在集群繁忙时可能会显得明显。

你可以在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter05/ansible/templates/cni/amazon-k8s-cni.yaml 找到完整的源代码。

现在,让我们为 amazon-vpc-cni-k8s 创建 Ansible 模板和配置:

  1. 定义配置变量并将其添加到以下路径的 group_vars 目录中:ansible/group_vars/all/cni.yaml。基本配置包含镜像及其标签,这对于跟踪已部署到集群中的 amazon-vpc-cni-k8s 版本并控制其升级非常有用。

    集群性能有两个重要的配置值:

    • MINIMUM_IP_TARGET,它对预扩展非常重要,因为它指定了在节点上为 Pod 分配的最小 IP 地址数量

    • WARM_IP_TARGET,它对动态扩展非常重要,因为它指定了 ipamD 守护进程应尝试保持可用的空闲 IP 地址数量,以供节点上 Pod 分配。

    这两个变量一起确保为新 Pods 提供足够的 IP 地址,从而改善 Pods 的启动时间,并提高集群的正常运行时间和恢复时间。您可以根据集群中运行的 Pods 预估数量以及高峰时段的数量来指定这些变量的值:

    cni_warm_ip_target: 2
    cni_min_ip_target: 10
    aws_cni:
      image: "602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon-k8s-cni"
      tag: "v1.6.2"
    
  2. 在此路径下创建 amazon-vpc-cni-k8s DaemonSet 的部署模板:ansible/templates/cni/amazon-k8s-cni.yaml

    以下代码片段是此模板的一部分,您需要修改的唯一代码行是定义 imageenv 规格的地方:

    ---
          containers:
            - image: {{ aws_cni.image }}:{{ aws_cni.tag }}
              imagePullPolicy: Always 
              env:
                - name: AWS_VPC_K8S_CNI_LOGLEVEL
                  value: DEBUG
                - name: AWS_VPC_K8S_CNI_VETHPREFIX
                  value: eni
                - name: AWS_VPC_ENI_MTU
                  value: "9001"
                - name: MINIMUM_IP_TARGET
                  value: "{{ cni_min_ip_target }}"
                - name: WARM_IP_TARGET
                  value: "{{ cni_warm_ip_target }}"
                - name: MY_NODE_NAME
                  valueFrom:
                    fieldRef:
                      fieldPath: spec.nodeName
    

您可以通过将其他选项添加到容器环境变量中来配置 amazon-vpc-cni-k8s,就像在 DaemonSet 模板中为容器部分编写的代码片段一样。

配置 CoreDNS

Kubernetes 曾使用 kube-dns 作为默认的集群 DNS 服务,但从版本 1.11 开始,它改用了 CoreDNS。此外,大多数托管的 Kubernetes 服务,包括我们在本书中使用的 EKS,都会预先部署 CoreDNS。

对于其他仍然使用 kube-dns 的 Kubernetes 托管服务,例如 GKE,我们建议参考 kube-dns 的官方文档。

CoreDNS 非常灵活,因为它是模块化和可插拔的。它拥有丰富的插件集,可以启用以增强 DNS 功能。这也是它强大且通常优于 kube-dns 和其他 Kubernetes DNS 解决方案的原因。要了解更多支持的插件,请参阅以下列表:coredns.io/plugins/

在集群生命周期中,您需要将 CoreDNS 配置作为代码进行管理,并定期更新,并将所有这些内容包含在集群的部署流水线中。同时,您还需要优化集群的 DNS 性能,并通过启用 CoreDNS 插件来添加额外的 DNS 功能。

推荐调整 CoreDNS 的资源配额(如 CPU 和内存)以提高集群 DNS 的性能,尤其是在集群大规模扩展的情况下。有关详细的资源配置和扩展,请查看此链接:github.com/coredns/deployment/blob/master/kubernetes/Scaling_CoreDNS.md#

重要提示

您可以在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter05/ansible/templates/core-dns/core-dns.yaml 查找本节的完整源代码。

现在,让我们为 coredns 创建 Ansible 模板和配置:

  1. 定义配置变量并将其添加到此路径中的group_vars目录:ansible/group_vars/all/core-dns.yaml。基础配置包含镜像及其标签,这对于跟踪部署到集群的 CoreDNS 版本并控制其升级非常有用。

    集群 DNS 的默认 IP 通常是172.20.0.10,除非你决定更改它。你可以通过设置副本数来指定整个集群中的 CoreDNS Pod 数量:

    core_dns_replicas: 2
    dns_cluster_ip: 172.20.0.10
    core_dns:
      image: "602401143452.dkr.ecr.us-east-1.amazonaws.com/eks/coredns"
      tag: "v1.6.6"
    
  2. 在此路径中创建 CoreDNS Pods 的部署模板:ansible/templates/core-dns/core-dns.yaml

    以下代码片段是此模板的一部分,模板中值得注意的配置是 CoreDNS 副本的数量和 Docker 镜像:

    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: coredns
      namespace: kube-system
      labels:
        k8s-app: kube-dns
        kubernetes.io/name: "CoreDNS"
        eks.amazonaws.com/component: coredns
    spec:
      replicas: {{ core_dns_replicas }}
    

    在以下代码片段中,你配置了 CoreDNS 镜像和标签:

    containers:
    - name: coredns
      image: {{ core_dns.image }}:{{ core_dns.tag }}
    
  3. 在以下代码片段中,你指定了ConfigMap CoreDNS,在其中你可以修改Corefile以启用额外的插件并微调其配置:

    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: coredns
      namespace: kube-system
      labels:
        eks.amazonaws.com/component: coredns
        k8s-app: kube-dns
    data:
      Corefile: |
        .:53 {
            errors
            health
            ready
            kubernetes cluster.local {
              pods insecure
              upstream
              fallthrough in-addr.arpa ip6.arpa
            }
            prometheus :9153
            forward . /etc/resolv.conf
            cache 300
            loop
            reload
            loadbalance
            autopath @kubernetes
        }
    

在前面的ConfigMap代码中,我们添加了额外的插件,这些插件有助于提高集群的 DNS 性能,如下所示:

  • ready:当所有能够信号就绪的插件完成时,端口8181上的 HTTP 端点将返回200 OK

  • loop:此插件会在检测到转发循环时停止 CoreDNS 进程。

  • reload:此插件会在Corefile发生变化时自动重新加载。

  • loadbalance:此插件会随机排列 DNS 记录的顺序,并作为一个轮询 DNS 负载均衡器。

  • autopath @kubernetes:此插件会跟随搜索路径元素链,并返回第一个不是NXDOMAIN的回复。

  • cache:此插件启用前端缓存。默认情况下启用;然而,默认的缓存持续时间为30秒,我们建议将此值增加到300秒,以便在大规模集群中获得更好的性能。

我鼓励你使用前面提到的 CoreDNS 插件,还可以查看plugins目录,里面可能有其他有趣且有用的插件,能解决特定问题或为你的应用提供更多选项,链接:coredns.io/manual/plugins/

配置 ExternalDNS

虽然 CoreDNS 充当 Kubernetes 集群的内部 DNS 服务器,但 ExternalDNS 是一个 Kubernetes 附加组件,用于管理集群的外部 DNS 提供商,包括 Route 53、AzureDNS 和 Google Cloud DNS。

它使 Kubernetes 部署和服务能够通过公共 DNS 服务(如 Route 53)进行发现。它查询 Kubernetes API 以检索服务和入口的列表,然后与公共 DNS 通信并注册这些记录。

ExternalDNS 允许你通过 Kubernetes 服务和入口动态地控制 DNS 记录(通过云 DNS 服务,如 AWS Route 53 或 Google Cloud DNS)。

ExternalDNS 并没有预安装在集群中,因此你需要部署它并指定其配置,包括其 Docker 镜像、要运行的副本数量、DNS 记录同步和间隔更新、云提供商类型(例如 AWS、Azure 等)以及托管区域 ID(以 AWS Route 53 为例)。

重要提示

你可以在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter05/ansible/templates/external-dns/external-dns.yaml 找到完整的源代码。

现在,让我们为 ExternalDNS 创建 Ansible 模板和配置:

  1. 定义配置变量,并将其添加到此路径中的 group_vars 目录:ansible/group_vars/all/external-dns.yaml。基本配置包含镜像及其标签,便于跟踪已部署到集群的 ExternalDNS 版本,并控制其升级。

    此外,你还需要为其他配置变量指定值,包括 log_levelprovideraws_zone_typeintervalroute53_zone_typeexternal_dns_replicas

    log_level: error
    provider: aws
    aws_zone_type: private
    interval: 1m
    route53_zone_id: Z09817802WZ9HZYSUI2RE
    external_dns_replicas: 2
    external_dns:
      image: "registry.opensource.zalan.do/teapot/external-dns"
      tag: "v0.5.9"
    
  2. 在以下路径创建 ExternalDNS Pod 的部署模板:ansible/templates/external-dns/external-dns.yaml

    在以下模板的代码片段中,你可以配置 ExternalDNS 的副本数量:

    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: external-dns
      namespace: kube-system
    spec:
      replicas: {{ external_dns_replicas }}
    
  3. 然后,你配置 ExternalDNS 镜像和标签,除了 ExternalDNS 的运行时配置变量,包括 log-levelsourceprovideraws-zone-idintervalregistrytxt-owner-id

        spec:
          serviceAccountName: external-dns
          containers:
          - name: external-dns
            image: {{ external_dns.image }}:{{ external_dns.tag }}
            args:
            - --log-level={{ log_level }}
            - --source=service
            - --source=ingress
            - --provider={{ provider }}
            - --aws-zone-type={{ aws_zone_type }}
            - --interval={{ interval }}
            - --registry=txt
            - --txt-owner-id={{ route53_zone_id }}-{{ cluster_name }}
    
  4. 为了使 ExternalDNS 正常运行,它需要访问 Route 53 的 DNS 资源。这就是为什么你需要创建以下 IAM 策略,以允许 ExternalDNS 列出托管的区域、列出 DNS 记录集并修改 DNS 记录:

    resource "aws_iam_policy" "external_dns_policy" {
      name        = "${var.cluster_full_name}-ExternalDNSPolicy"
      path        = "/"
      description = "Allows workers nodes to use route53 resources"
      policy = <<EOF
    {
     "Version": "2012-10-17",
     "Statement": [
       {
         "Effect": "Allow",
         "Action": [
           "route53:ChangeResourceRecordSets"
         ],
         "Resource": ["*"]
       },
       {
         "Effect": "Allow",
         "Action": [
           "route53:ListHostedZones",
           "route53:ListResourceRecordSets"
         ],
         "Resource": ["*"]
       }
     ]
    }
    EOF
    }
    

    如果你没有创建上述 IAM 策略并将其附加到工作节点或 Pod 上,则 ExternalDNS 将无法正常运行。

ExternalDNS 可以配置为使用大多数 DNS 提供商,包括 AzureDNS、Google Cloud DNS、CloudFlare 和 DNSimple。

要获取更多关于如何将 ExternalDNS 与你的 DNS 提供商以及 Kubernetes 部署一起使用的详细信息和代码示例,请查看官方文档:github.com/kubernetes-sigs/external-dns

配置 NGINX Ingress Controller

有三种主要方式可以将 Kubernetes 服务暴露到外部:NodePort、负载均衡器和 Ingress。本节将重点讨论 Ingress,因为它满足了大多数工作负载和 Kubernetes 集群中部署的需求。

Ingress 暴露 TCP/IP L7 服务(如 HTTP/HTTPS),并将来自集群外部的流量路由到集群内的服务。Ingress 通过为每个 Ingress 资源定义的一组规则和/或所有 Ingress 资源的全局配置来控制流量路由。

Ingress 可以控制许多配置,包括为服务提供外部 URL、SSL/TLS 终止、会话有效性和基于名称的虚拟主机。Ingress 控制器是负责履行 Ingress 的 Kubernetes 资源。

最受欢迎且经过充分测试的 Ingress 是 NGINX Ingress Controller。它是一个 Kubernetes 的 Ingress 控制器,使用 NGINX 作为反向代理和负载均衡器。

NGINX Ingress Controller 并未预装在集群中,因此您需要在集群上部署并配置它,包括其 Docker 镜像、运行的副本数量、运行时参数以及服务和云负载均衡器规格。

重要说明

您可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter05/ansible/templates/ingress-nginx/ingress-nginx.yaml找到完整的源代码。

现在,让我们创建ingress-nginx的 Ansible 模板和配置:

  1. 创建配置变量并将其添加到此路径中的group_vars目录:ansible/group_vars/all/ingress-nginx.yaml。基本配置包含nginx-ingress-controller及其 webhook 的镜像。这对于跟踪部署到集群中的ingress-nginx版本以及控制其升级非常有用:

    nginx_ingress_controller:
      image: "quay.io/kubernetes-ingress-controller/nginx-ingress-controller"
      tag: "0.32.0"
    nginx_ingress_webhook_certgen:
      image: "jettech/kube-webhook-certgen"
      tag: "v1.2.0"
    
  2. 在此路径下创建ingress-nginx部署的模板:ansible/templates/ingress-nginx/ingress-nginx.yaml

    ---
    apiVersion: apps/v1
    kind: Deployment
    
  3. 在下面的代码片段中,部署从ingress-nginx group_vars目录获取容器镜像的值:

        spec:
          dnsPolicy: ClusterFirst
          containers:
            - name: controller
              image: {{ nginx_ingress_controller.image }}:{{ nginx_ingress_controller.tag }}
    
  4. 在下面的代码片段中,您创建一个ConfigMap来配置nginx-ingress

    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/component: controller
      name: ingress-nginx-controller
      namespace: ingress-nginx
    data:
    
  5. 在下面的代码片段中,您创建了用于将nginx-ingress控制器暴露给公网的服务。这是通过配置 AWS nginx-ingress服务来实现的:

    ---
    apiVersion: v1
    kind: Service
    metadata:
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
        service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: '60'
        service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: 'true'
        service.beta.kubernetes.io/aws-load-balancer-type: nlb
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/component: controller
      name: ingress-nginx-controller
      namespace: ingress-nginx
    spec:
      type: LoadBalancer
      externalTrafficPolicy: Local
    

完成使用 Ansible 模板创建网络服务和附加组件后,您准备好部署它们并将 Ansible 剧本应用到您的集群。在接下来的部分中,您将使用在上一章中创建的packtclusters-prod1集群来应用所有这些更改。

部署集群的网络服务

以下指令将部署 Ansible 剧本并使用网络服务和附加组件配置来配置您的集群:

  1. 通过运行以下命令初始化 Terraform 状态并选择工作区:

    $ cd terraform/packtclusters
    $ terraform init
    $ terraform workspace select prod1
    
  2. 执行 Terraform 以应用本章中添加的基础设施——IAM 策略和 ExternalDNS 的策略附加:

    $ terraform apply -auto-approve
    

    然后你应该会看到以下输出:

    Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
    Releasing state lock. This may take a few moments...
    
  3. 获取并配置目标集群的 kubeconfig

    $ aws eks --region $(terraform output aws_region) update-kubeconfig --name $(terraform output cluster_full_name)
    
  4. 创建 virtualenv 来安装和执行 Ansible:

    $ virtualenv $HOME/Ansible-k8s-workspace
    $ source $HOME/Ansible-k8s-workspace/bin/activate
    
  5. 安装 Ansible 以及所需的模块 openshiftpyyamlrequests

    $ pip install Ansible==2.8.10 openshift pyyaml requests
    
  6. 执行 Ansible playbook:

    $ Ansible-playbook -i \
    ../../Ansible/inventories/packtclusters/ \
    -e "worker_iam_role_arn=$(terraform output worker_iam_role_arn)" \
    ../../Ansible/cluster.yaml
    
  7. 执行 Ansible 成功后,你将看到以下输出:图 5.1 – Ansible 执行输出

    图 5.1 – Ansible 执行输出

  8. 执行以下 kubectl 命令以获取集群中运行的所有 Pod。这允许你验证集群配置是否已成功应用:

    $ kubectl get pods --all-namespaces
    

    你应该会看到以下输出,列出了集群中运行的所有 Pod,包括网络附加组件的新 Pod:

图 5.2 – 所有 Pod 的列表

图 5.2 – 所有 Pod 的列表

现在,你已经完成了按照前述指示应用集群配置的工作,并且集群已经部署并配置了所有网络服务和附加组件,准备好承担生产工作负载。

销毁集群资源

首先,你应该删除 ingress-nginx 服务,指示 AWS 销毁与入口控制器相关联的 NLB。此步骤是必需的,因为 terraform 将无法销毁此 NLB,因为它是由 Kubernetes 创建的:

$ kubectl -n nginx-ingress delete svc nginx-ingress

然后,你可以按照第三章《构建和配置 Kubernetes 集群》一节中的剩余说明,销毁 Kubernetes 集群和所有相关的 AWS 资源。请确保资源按照以下顺序被销毁:

  1. Kubernetes 集群 packtclusters 资源

  2. 集群 VPC 资源

  3. Terraform 共享状态资源

执行前述步骤后,所有 Kubernetes 和 AWS 基础设施资源应该已被销毁并清理干净,为下一章的实操练习做准备。

总结

在本章中,你了解了 Kubernetes 网络组件和服务,这些组件和服务使集群能够投入生产环境。你使用 Ansible 开发了这些服务的模板和配置代码。

尽管这些组件中的一些是随 AWS EKS 一起预部署的,但你仍然需要对它们的配置进行微调,以满足集群在扩展性、可用性、安全性和性能方面的要求。你还部署了额外的附加组件和服务,包括 ExternalDNS 和 NGINX Ingress Controller,它们对于 Kubernetes 的网络需求至关重要。

通过使用我们在前一章中介绍的 Ansible 配置管理解决方案,编写这些服务的 Kubernetes 清单变得简单、可扩展且易于维护。我们遵循相同的框架和步骤来配置每个服务,这一过程会重复应用于本书中您将开发的所有服务和附加配置。

本章介绍了 Kubernetes 集群的网络生产就绪性,但还有相关主题将在后续章节中涉及,包括网络安全、网络策略、服务网格和网络服务可观察性。

在下一章,您将详细了解 Kubernetes 安全;包括安全最佳实践、工具、附加组件和配置,这些是您在生产级集群中部署和优化所必需的。

进一步阅读

您可以参考以下链接,了解本章涵盖的更多信息:

第六章:第六章:有效地保护 Kubernetes

在之前的章节中,你学习了如何设计和配置 Kubernetes 集群的基础设施,微调它们的配置,并在集群上部署额外的插件和服务,例如网络、安全、监控和扩展。

在本章中,你将学习 Kubernetes 安全的不同方面,重点是使集群具备生产级别的安全性。我们将采取端到端的安全方法,涵盖每个生产集群应具备的所有重要领域。我们将了解如何通过微调集群及其基础设施的安全配置、部署新的安全插件和工具,最终确保集群的安全合规性以及遵循安全标准和检查,从而使集群的安全性更加接近生产准备状态。

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

  • 保护 Kubernetes 基础设施

  • 管理集群访问

  • 管理密钥和证书

  • 保护工作负载和应用程序

  • 确保集群安全和合规性

  • 额外的安全提示

  • 部署安全配置

  • 销毁集群

技术要求

你应该已经在之前的章节中安装了以下工具:

  • AWS CLI V2

  • AWS IAM Authenticator

  • kubectl

  • Terraform

  • Python3

  • PIP 3

  • virtualenv

  • 你需要有一个正在运行的 Kubernetes 集群

本章的代码可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter06获取。

查看以下链接,观看《代码实践》视频:

bit.ly/2MBwZNk

保护 Kubernetes 基础设施

第二章构建生产级 Kubernetes 基础设施 中,我们讨论了 Kubernetes 集群网络基础设施的最佳实践,并提出了对集群基础设施安全至关重要的设计指南。虽然这些指南对于你考虑和遵循非常重要,但你仍然需要评估整个网络安全需求,以确保你为你的环境和产品提供了完整且适当的安全解决方案。

这些安全建议和最佳实践大多已经在我们之前章节中使用的 Terraform 和 Ansible 配置中实现:

  • 使用多个可用区(三个或更多)来部署你的 Kubernetes 集群,以确保高可用性。

  • 仅在私有子网中部署控制平面和工作节点。将公共子网用于面向互联网的负载均衡器。

  • 不允许公共访问工作节点。通过负载均衡器或入口控制器对外暴露服务,而不是通过节点端口。

  • 在 API 服务器与其他控制平面组件或工作节点之间传输的所有流量都应通过 TLS 进行加密。

  • 限制对 Kubernetes API 端点的网络访问。

  • 阻止对 kubelet 的访问。

  • 使用安全组来阻止对工作节点和控制平面端口的访问,除非是安全端口。

  • 禁用对工作节点的 SSH 访问。您可以使用 AWS Systems Manager Session Manager 来代替运行 SSHD 连接到节点。

  • 限制对 EC2 实例配置文件凭证的访问。默认情况下,Pod 中的容器使用与节点实例配置文件关联的相同 IAM 权限。这被认为是一种不安全的行为,因为它赋予容器对节点和底层 AWS 服务的完全控制。为了避免这种行为,您必须通过在节点内执行以下 iptables 命令来禁用 Pod 对节点实例配置文件的访问:

    kube2iam add-on. It manages the pod's IAM access, and it will block the containers from accessing the instance profile credentials. You will learn about kube2iam in detail later in this chapter.
    
  • 由于我们使用的是 EKS,强烈建议使用常规的 kubelet 代理。避免使用 EKS 节点组的另一个原因是它强制要求工作节点附加公共 IP,这可能带来安全威胁。

上述列表涵盖了 Kubernetes 集群生产基础设施安全的基本指南。所有这些指南都通过集群配置和管理来涵盖,我们在前几章中实现了这些内容。值得一提的是,您的集群基础设施可能有额外的安全要求,您应在基础设施设计和部署过程中加以考虑。

管理集群访问

集群用户的请求,无论是人类用户还是服务账户,都需要通过认证和授权阶段,然后才能到达 API 服务器并操作所需的 Kubernetes 对象。一个典型的请求需要经过三个访问阶段,才能决定是否被允许或拒绝:

![图 6.1 – Kubernetes 访问阶段图 6.1 – Kubernetes 访问阶段

图 6.1 – Kubernetes 访问阶段

请求必须通过认证阶段,使用 Kubernetes 支持的任一机制验证客户端身份,然后通过授权阶段验证该用户允许执行的操作,最后通过准入控制阶段决定是否需要进行任何修改。您将在以下子章节中了解这些过程。

集群认证

Kubernetes 集群用户需要成功认证才能访问集群对象。然而,普通的集群用户,如开发人员和管理员,不应由 Kubernetes 管理,而应由集群外部的外部服务进行管理,如 轻量级目录访问协议 (LDAP)、OpenID Connect (OIDC)、AWS 身份与访问管理 (IAM),甚至是一个包含用户和密码对的文件。另一方面,服务账户由 Kubernetes 管理,您可以使用 Kubernetes API 调用来添加或删除它们。

作为集群所有者,您需要决定如何管理集群的普通用户,换句话说,选择使用哪个外部服务进行认证。对于生产集群的用户认证,我们建议使用 AWS IAM 作为认证服务。但也可以使用 OIDC 身份提供者,如 Azure Active Directory 或 GitHub。

值得一提的是,Kubernetes 针对不同的认证方式(如客户端 TLS 证书、密码和令牌)有不同的认证模块。集群管理员可以在集群配置过程中配置其中的一部分或全部。

使用 AWS IAM 进行用户认证

EKS 支持 webhook 令牌认证和服务账户令牌。Webhook 认证验证承载令牌。这些承载令牌由 aws-iam-authenticator 客户端在执行 kubectl 命令时生成。然后,令牌会传递到 kube-apiserver,在转发到认证 webhook 之前,认证 webhook 返回用户的账户和 ARN 给 kube-apiserver

一旦用户的身份通过 AWS IAM 服务认证,kube-apiserver 会读取 kube-system 命名空间中的 aws-auth ConfigMap,以确定 aws-auth ConfigMap 用于在 IAM 用户、角色和 Kubernetes RBAC 组之间创建映射,以便进行授权。这些 RBAC 组可以在 Kubernetes ClusterRoleBindings 或 RoleBindings 中引用。

我们已经在第四章《使用 Ansible 管理集群配置》中了解了如何创建自定义的 aws-auth ConfigMap,在该章节中,我们可以添加用户可以承担的 IAM 用户和 IAM 角色,以访问集群。请在此查看 aws-auth ConfigMap 的完整配置代码:github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter06/ansible/templates/auth/aws-auth.yaml

我们建议使用 IAM 角色来管理生产集群的访问权限,您可以将这些 IAM 角色分配给 IAM 组和用户,这使得 Kubernetes 认证更易于操作和扩展。

修改 EKS 集群创建者

值得注意的是,EKS 会授予创建集群的 IAM 用户或 IAM 角色对集群 Kubernetes API 服务的永久管理员认证。AWS 并未提供任何方式来更改此设置或将其转移到不同的 IAM 用户或角色。为了尽量减少这一限制的安全隐患,我们建议采取以下措施:

  1. 使用专门的临时 IAM 角色来配置每个新的集群。

  2. 配置完集群后,删除此角色的所有 IAM 权限。

  3. 更新 kube-system 命名空间中的 aws-auth ConfigMap,添加更多的 IAM 用户和角色,以便能够管理和使用集群。

  4. 根据需要将这些组作为RoleBindingsClusterRoleBindings的主体,添加到集群的 RBAC 中。

你已经在第四章使用 Ansible 管理集群配置中学到了如何在 Ansible 集群配置中处理这一缺陷,我们创建了自定义的aws-auth ConfigMap 和ClusterRoleBindings

集群授权

集群访问的第二个阶段是授权。这决定了请求的操作是否被允许。为了使 Kubernetes 授权请求,它考虑三个输入;首先是发起请求的用户,然后是请求的操作,最后是操作将修改的 Kubernetes 资源,如 pods 和服务。

当你创建集群时,你通过将其值传递给 API 服务器来配置授权模式。然而,在 EKS 中,所有授权模式(RBAC、基于属性的访问控制和 Webhooks)默认启用,Kubernetes 将检查它们以授权请求。

审批控制器

集群访问的最终阶段是通过审批控制器。在此步骤中,请求根据审批控制器定义的规则和请求的对象进行验证。还有另一种类型的审批控制器,称为变更控制器(mutating controller),它可以修改请求,例如注入 side car 容器或在将请求发送到kube-api-server之前修改 pod 规格。

审批控制器是一种强大的授权机制,可以通过集群用户或第三方进行扩展,以在集群用户上执行特殊的验证和规则。

管理机密和证书

机密和 TLS 证书是现代应用程序的基本安全需求,虽然 Kubernetes 提供了原生解决方案来创建和使用机密及敏感数据,但仍需要进一步的加固。另一方面,Kubernetes 没有原生的证书颁发和管理解决方案,这就是我们将部署其中一个流行的附加组件并使用它来实现这一目的的原因。

创建和管理机密

Kubernetes 具有一种机密资源类型,可以用来存储敏感数据,例如密码、令牌、证书和 SSH 密钥。Pods 可以通过将它们挂载为卷或环境变量来使用这些机密。然而,我们不推荐使用环境变量,因为它们可能泄露并被篡改。

这里的另一个挑战出现在用户决定将包含机密的 YAML 清单存储在 Git 仓库时。在这种情况下,敏感数据容易受到泄露,因为机密没有使用加密,而是采用 Base64 编码,而 Base64 编码可以轻松解码。

封装机密通过提供一种加密机密敏感数据的机制来解决此问题,使其可以安全地存储在 Git 仓库中。

封装机密由两部分组成:

  1. 一种命令行工具,kubeseal,用于将自定义资源定义CRD)的机密转换为封装的机密。

  2. Sealed Secrets 控制器用于生成加密密钥,并将密封的秘密解密为供 pods 使用的秘密。

要了解有关 Sealed Secrets 和 kubeseal 客户端的更多信息,请查看以下链接:github.com/bitnami-labs/sealed-secrets

它是这样工作的。kubeseal 与 Sealed Secrets 控制器通信,获取加密公钥,然后使用该公钥将秘密 CRD 加密成密封的秘密 CRD。当 pod 需要使用密封的秘密时,控制器会使用加密私钥解密密封的秘密 CRD,并将其转换为常规的秘密 CRD。

值得一提的是,Sealed Secrets 通过引入作用域的概念,在多租户集群中缓解了与秘密相关的安全风险。它限制了在命名空间内或集群范围内的秘密使用和操作,并且可以限制或更改秘密的名称和命名空间。有关这一点的详细原因,可以在官方文档中找到:github.com/bitnami-labs/sealed-secrets#scopes

现在,让我们创建 Ansible 模板和配置,将 Sealed Secrets 控制器部署到集群中:

  1. 定义所需的配置变量,并将其添加到 group_vars 目录中的此路径 —— ansible/group_vars/all/sealed-secrets.yaml。基本配置包括部署副本数量和镜像标签,有助于跟踪已部署的版本并控制其升级:

    sealed_secrets_replicas: 1
    seald_secrets:
      image: "quay.io/bitnami/sealed-secrets-controller"
      tag: "v0.12.4"
    

    重要提示

    你可以在此链接找到 Sealed Secrets 部署模板的完整源代码:github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter06/ansible/templates/sealed-secrets

  2. 在以下路径创建 Sealed Secrets 控制器的部署模板 —— ansible/templates/sealed-secrets/sealed-secrets.yaml。在该控制器中,我们将仅设置部署副本和镜像标签的变量。你可以在此链接查看完整的清单 YAML 文件:github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter06/ansible/templates/sealed-secrets/sealed-secrets.yaml

  3. 按照以下方式为 macOS 安装 kubeseal CLI:

    $ brew install kubeseal
    

    使用以下命令为 Linux 安装:

    $ wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.12.4/kubeseal-linux-amd64 -O kubeseal
    $ sudo install -m 755 kubeseal /usr/local/bin/kubeseal
    

要部署 Sealed Secrets 控制器,请按照本章最后“部署安全配置”部分的部署步骤进行操作。

使用 Cert-Manager 管理 TLS 证书

Cert-Manager 是一个 Kubernetes 插件和控制器,允许从不同来源颁发证书,例如 SelfSigned、CA、Vault 和 ACME/Let's Encrypt,以及外部颁发者,如 AWS 私有证书颁发机构和 AWS 密钥管理服务。它还确保证书的有效性,并自动续期和轮换证书。你可以在这里了解更多关于该项目的信息:cert-manager.io/docs/

Cert-Manager 将为 Kubernetes 工作负载提供现成的 TLS 证书,它使得在 Kubernetes 集群内发放和管理这些证书成为一个本地特性,易于管理和操作。

Cert-Manager 并不会预装在集群中,所以你需要自行部署它并指定其配置,包括其 Docker 镜像、要运行的副本数、证书颁发机构、DNS Route 53 区域等。

为了部署 Cert-Manager,我们将创建三个 Kubernetes 清单文件:命名空间、控制器和证书颁发者。

Cert-Manager 支持多种证书颁发者。请在这里查看:cert-manager.io/docs/configuration/。在本章中,我们决定使用 Let's Encrypt,因为它是免费的并且被广泛使用,但你可以使用 Cert-Manager 文档,并使用相同的部署来支持其他任何颁发者。

现在,让我们创建 Ansible 模板并为其配置:

重要提示

你可以在这里找到 Cert-Manager 部署模板的完整源代码:github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter06/ansible/templates/cert-manager

  1. 定义所需的配置变量并将其添加到这个路径下的group_vars目录中 – ansible/group_vars/all/cert-manager.yaml。基本配置包含部署副本数和控制器、webhook 以及cainjector的镜像标签,这对于跟踪已部署的版本和控制升级非常有用。此外,还包含了prodnonprod ACME URL 的 Let's Encrypt 颁发者配置:

    log_level: error
    letsencrypt_email: security@packt.com
    letsencrypt_prod_url: https://acme-v02.api.letsencrypt.org/directory
    letsencrypt_nonprod_url: https://acme-staging-v02.api.letsencrypt.org/directory
    cert_manager_replicas: 1
    cert_manager_controller:
      image: "quay.io/jetstack/cert-manager-controller"
      tag: "v0.15.2"
    cert_manager_cainjector:
      image: "quay.io/jetstack/cert-manager-cainjector"
      tag: "v0.15.2"
    cert_manager_webhook:
      image: "quay.io/jetstack/cert-manager-webhook"
      tag: "v0.15.2"
    
  2. 在这个路径下创建 Cert-Manager 的命名空间 – ansible/templates/cert-manager/namespace.yaml

    ---
    apiVersion: v1
    kind: Namespace
    metadata:
      name: cert-manager
      labels:
        certmanager.k8s.io/disable-validation: "true"
    
  3. 在这个路径下创建 Cert-Manager 控制器资源的部署模板 – ansible/templates/cert-manager/cert-manager.yaml。在这个控制器中,我们将只设置部署副本数和镜像标签的变量。你可以在这里查看完整的清单 YAML 文件:github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter06/ansible/templates/cert-manager/cert-manager.yaml

  4. 在路径ansible/templates/cert-manager/letsencrypt-clusterissuer.yaml中创建 Let's Encrypt 的发行者配置。在此文件中,有两个配置,第一个是用于生产工作负载的证书,另一个是用于非生产工作负载的证书。主要的区别在于,Let's Encrypt 会允许你为非生产环境发行任意数量的证书,但对于生产环境的证书,每周的数量是有限的:

    ---
    apiVersion: certmanager.k8s.io/v1alpha1
    kind: ClusterIssuer
    metadata:
      name: letsencrypt-prod
    spec:
      acme:
        email: {{ letsencrypt_email }}
        server: {{ letsencrypt_prod_url }}
        privateKeySecretRef:
          name: letsencrypt-prod
        solvers:
        - http01:
            ingress:
              class: nginx
        - selector:
            matchLabels:
              use-dns01-solver: "true"
          dns01:
            route53:
              region: {{ aws_default_region }}
              hostedZoneID: {{ route53_zone_id }}
    

    上述发行者配置的第二部分与生产发行者非常相似,但使用了不同的 Let's Encrypt 服务器。

要部署 Cert-Manager 插件,请在本章末尾的部署安全配置部分中应用部署步骤。

这里是如何使用 Cert-Manager 和 Let's Encrypt 并将其与 Ingress 控制器和域名关联的示例:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  name: test-ingress
  namespace: test-ingress
spec:
  rules:
  - host: example.com
    http:
      paths:
      - backend:
          serviceName: myservice
          servicePort: 80
        path: /
  tls: 
  - hosts:
    - example.com
    secretName: example-cert

之前的Ingress资源使用 Cert-Manager 注解来连接到 Let's Encrypt TLS 生产证书发行者,并定义了一个主机,使用样本 DNS example.comsecretNameexample-cert,Cert-Manager 将在此存储从 Let's Encrypt 获取的 TLS 证书,并供该Ingress资源使用。你也可以使用相同的Ingress资源,但搭配一个你拥有的域名。

要了解如何在其他使用场景中使用 Cert-Manager,请查看官方文档:cert-manager.io/docs/usage/

确保工作负载和应用程序的安全

Kubernetes 提供了不同的内置和第三方解决方案,以确保你的生产工作负载安全运行。在进入生产前,我们将探讨我们认为对你的集群至关重要的配置,如工作负载隔离技术、Pod 安全策略、网络策略和监控工作负载运行时安全。

隔离关键工作负载

Kubernetes 设计上每个集群只有一个控制平面,这使得在多个租户和工作负载之间共享单一集群变得具有挑战性,并且要求集群所有者拥有清晰的集群多租户和资源共享策略。

在需要处理租户和工作负载隔离的不同使用场景中,这一点至关重要:

  • 在许多组织中,有多个团队、产品或环境共享一个集群。

  • 有些情况是你将 Kubernetes 作为服务提供给你自己或外部组织。

  • 此外,常见的一种情况是,当你的 Kubernetes 基础设施用于提供软件即服务SaaS)产品时。

对于前述的使用场景,我们需要确保集群具备所需的工作负载隔离配置,通过使用各种 Kubernetes 对象,如命名空间、RBAC、配额和限制范围来实现软多租户。这也是你在本节及本章中将要学习的内容。

现在,我们需要探讨实现租户隔离的不同技术,同时减少与 Kubernetes 单租户设计相关的风险。

使用命名空间

命名空间是 Kubernetes 提供的第一层隔离机制。它们为 Kubernetes 资源创建边界,提供了软多租户机制。许多 Kubernetes 安全控制,例如网络策略、访问控制、密钥、证书以及其他重要的安全控制,都可以在命名空间级别进行作用域限制。通过将租户工作负载分配到各自的命名空间,您将能够限制安全攻击的影响,以及集群用户的有意或无意的错误。

创建独立的节点组

我们通常避免使用特权容器,但在某些情况下,例如系统 pod 或产品特定的技术要求,这些容器是不可避免的。为了减少安全漏洞的影响,我们将这些 pod 隔离在专用节点和节点组上,这样其他租户的工作负载就无法在这些节点上调度。对于包含敏感数据的 pod 也可以采取相同的做法。该方法可以减少敏感数据被共享工作节点上不太安全的应用程序访问的风险。然而,它也带来了一定的缺点,例如可能会增加基础设施成本,在做出这一设计决策时,您需要权衡安全性与成本之间的关系。

实施严格的多租户架构

在特定的使用场景下,严格的多租户架构是必须的,这通常是由于法律和监管要求。在这种情况下,可以通过为每个租户提供独立的集群来实现多租户架构,这就是我们所说的严格多租户架构。然而,这种方式也有其缺点,例如随着集群数量的增加,管理这些集群的挑战、整体成本的增加以及每个集群的计算资源利用率下降。

强化默认的 pod 安全策略

Pod 安全策略 (PSP) 是一种 Kubernetes 资源,用于确保 pod 在创建之前必须满足特定的要求。

PSPs 有不同的安全设置,您可以通过增加或减少 pod 权限来进行配置,包括允许容器的 Linux 能力、主机网络访问以及文件系统访问等方面。

仍然值得一提的是,PSP 仍处于测试版阶段,对于拥有严格生产政策的公司来说,部署测试版特性并不受欢迎。

您可以在集群中定义多个 PSP,并将它们分配给不同类型的 pod 和命名空间,以确保每个工作负载和租户都具有正确的访问权限。EKS 集群自带一个默认的 PSP,名为 eks.privileged,它会在您创建集群时自动生成。您可以通过描述该 PSP 来查看 eks.privileged PSP 的规格:

$ kubectl describe psp eks.privileged
Name:  eks.privileged
Settings:
  Allow Privileged:                       true
  Allow Privilege Escalation:             0xc0004ce5f8
  Default Add Capabilities:               <none>
  Required Drop Capabilities:             <none>
  Allowed Capabilities:                   *
  Allowed Volume Types:                   *
  Allow Host Network:                     true
  Allow Host Ports:                       0-65535
  Allow Host PID:                         true
  Allow Host IPC:                         true
  Read Only Root Filesystem:              false
  SELinux Context Strategy: RunAsAny
    User:                                 <none>
    Role:                                 <none>
    Type:                                 <none>
    Level:                                <none>
  Run As User Strategy: RunAsAny
    Ranges:                               <none>
  FSGroup Strategy: RunAsAny
    Ranges:                               <none>
  Supplemental Groups Strategy: RunAsAny
    Ranges:                               <none>

这个默认的 eks.privileged PSP 允许任何经过身份验证的用户在所有命名空间中运行特权容器。此行为旨在允许像 AWS VPC CNI 和 kube-proxy 这样的系统 Pod 以特权模式运行,因为它们负责配置主机的网络设置。然而,你必须限制其他类型的 Pod 和命名空间的此类行为。

作为最佳实践,我们建议将特权 Pod 限制为 kube-system 命名空间中的服务账户,或任何你用来隔离系统 Pod 的其他命名空间。对于所有托管其他类型 Pod 的命名空间,我们建议分配一个限制性的默认 PSP。以下清单定义了一个 PSP,用于限制特权 Pod,并禁止访问主机网络。我们将把这个清单添加到我们的 Ansible 模板的自动化路径中:ansible/templates/psp/default-psp.yaml

---
apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: default-psp
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default'
    apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default'
    seccomp.security.alpha.kubernetes.io/defaultProfileName:  'runtime/default'
    apparmor.security.beta.kubernetes.io/defaultProfileName:  'runtime/default'

以下代码片段定义了默认 PSP 的规范。它不允许特权容器,禁用容器特权提升,并删除所有 Linux 能力:

spec:
  privileged: false
  defaultAllowPrivilegeEscalation: false
  allowedCapabilities: []
  requiredDropCapabilities:
    - ALL

你可以在这里查看之前 PSP 资源的完整源代码:github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter06/ansible/templates/psp

以下 ClusterRole 定义允许所有绑定到它的角色使用之前的 default-psp PSP:

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: default-psp-user
rules:
- apiGroups:
  - extensions
  resources:
  - podsecuritypolicies
  resourceNames:
  - default-psp
  verbs:
  - use

以下 ClusterRoleBinding 定义将 default-psp-user ClusterRole 绑定到 system:authenticated RBAC 用户组,这意味着任何被添加到集群 RBAC 用户组 system:authenticated 的用户,都必须创建符合 default-psp PSP 的 Pod:

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: default-psp-users
subjects:
- kind: Group
  name: system:authenticated
roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: ClusterRole
   name: default-psp-user

你可以根据安全要求创建额外的 Pod 安全策略,但基本上,你的集群需要有两种 Pod 安全策略;第一种是 eks.privileged,用于 kube-system 命名空间中的 Pod,第二种是 default-psp,用于任何其他命名空间。

限制 Pod 访问

通常,Pod 需要访问底层的云服务,例如对象存储、数据库和 DNS。理想情况下,你不希望生产环境中的 Pod 访问所有服务,或访问它们不应该使用的服务。这就是为什么我们需要将 Pod 访问限制为它们所用的服务。

在 AWS 环境中,可以通过利用 IAM 角色并将此角色及访问策略附加到 Pod 来实现这一点。kube2iam 是 Kubernetes 的一个附加组件,可以高效地完成这项工作。它是一个经过实际生产环境考验的开源项目,易于部署、配置和使用。你可以在这里了解更多信息:github.com/jtblin/kube2iam

kube2iam并未预安装在集群中,因此你需要部署它并指定其配置,包括其 Docker 镜像、iptables 控制和主机网络接口。

现在,让我们为它们创建 Ansible 模板和配置:

重要提示

你可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter06/ansible/templates/kube2iam找到完整的源代码。

  1. 定义所需的配置变量并将其添加到此路径中的group_vars目录——ansible/group_vars/all/kube2iam.yaml。基本配置包含kube2iam DaemonSet 的镜像标签,这对于跟踪已部署版本和控制其升级非常有用:

    kube2iam:
      image: "jtblin/kube2iam"
      tag: "0.10.9"
    
  2. 在此路径中创建 Cert-Manager 控制器资源的部署模板——ansible/templates/cert-manager/cert-manager.yaml。在该控制器中,我们将仅设置部署副本数和镜像标签的变量:

    ---
    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: kube2iam
      namespace: kube-system
      labels:
        app: kube2iam
    

    以下代码片段是kube2iam DaemonSet 的规范。规格中最重要的部分是容器运行时参数的部分:

    spec:
    ---
          containers:
            - image: {{ kube2iam.image }}:{{ kube2iam.tag }}
              name: kube2iam
              args:
                - "--auto-discover-base-arn"
                - "--auto-discover-default-role=true"
                - "--iptables=true"
                - "--host-ip=$(HOST_IP)"
                - "--node=$(NODE_NAME)"
                - "--host-interface=eni+"
                - "--use-regional-sts-endpoint"
    

    之前 YAML 文件中最显著的配置参数是"--iptables=true",它允许kube2iam添加 iptables 规则,以阻止 Pods 访问底层工作节点的实例配置文件。

要部署kube2iam,请按照本章最后部分部署集群安全配置中的步骤进行操作。

要在 Pod 中使用kube2iam,你必须将iam.amazonaws.com/role annotation添加到 Pod 注释部分,并添加 Pod 要使用的 IAM 角色。以下是一个示例,说明如何在 Pods 中使用kube2iam

apiVersion: v1
kind: Pod
metadata:
  name: aws-cli
  labels:
    name: aws-cli
  annotations:
    iam.amazonaws.com/role: <add-role-arn-here>
spec:
  containers:
  - image: fstab/aws-cli
    command:
      - "/home/aws/aws/env/bin/aws"
      - "s3"
      - "ls"
      - "add-any-bucket-name-here"
    name: aws-cli

上述 Pod 将运行一个aws-cli容器,该容器执行 S3 列表命令以获取某个桶的内容。请确保在注释部分用有效的 IAM 角色 ARN 替换占位符,并在容器命令部分用有效的 S3 桶名称替换。

使用 Calico 创建网络策略

集群内所有 Pod 之间的通信默认是允许的。这种行为是不安全的,尤其是在多租户集群中。之前,你已经学习了集群网络基础设施,以及如何使用安全组控制集群节点之间的网络流量。然而,安全组在控制 Pod 之间的流量时并不起作用。这就是 Kubernetes 提供网络策略 API的原因。这些网络策略允许集群的用户强制执行入口和出口规则,以允许或拒绝 Pod 之间的网络流量。

Kubernetes 定义了网络策略 API 规范,但并未提供内建的能力来强制执行这些网络策略。因此,要强制执行它们,你必须使用网络插件,如 Calico 网络策略。

你可以通过使用以下 kubectl 命令检查集群中是否有生效的网络策略:

$ kubectl get networkpolicies --all-namespaces
No resources found.

Calico 是一个可以部署到 Kubernetes 的网络策略引擎,它与 EKS 也能顺利配合使用。Calico 实现了 Kubernetes 的所有网络策略功能,但还支持一组额外的、更丰富的功能,包括策略顺序、优先级、拒绝规则和灵活的匹配规则。Calico 网络策略可以应用于不同类型的端点,包括 Pod、虚拟机和主机接口。与 Kubernetes 的网络策略不同,Calico 策略可以应用于命名空间、Pod、服务账户,或在集群范围内全局应用。

创建默认拒绝策略

作为安全最佳实践,网络策略应允许最小特权访问。你可以通过创建一个拒绝所有流量的策略,使用 Calico 全球限制所有入站和出站流量。

以下 Calico 全局网络策略实现了在集群中默认的拒绝所有入口和出口流量的策略:

apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: default-deny
spec:
  selector: all()
  types:
  - Ingress
  - Egress

一旦你有了默认的拒绝所有流量的网络策略,你可以在需要时为你的 Pod 添加允许规则。其中一个策略是添加一个全局规则,允许 Pod 查询 CoreDNS 进行 DNS 解析:

apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: allow-dns-egress
spec:
  selector: all()
  types:
  - Egress
  egress:
  - action: Allow
    protocol: UDP  
    destination:
      namespaceSelector: name == "kube-system"
      ports: 
      - 53

上述策略将允许任何命名空间中的 Pod 发出的出口网络流量查询位于 kube-system 命名空间中的 CoreDNS。

EKS 默认并未安装 Calico,因此我们将在 Ansible 配置中包含它。你可以在这里查看完整的源代码:github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter06/ansible/templates/calico-np

使用 Falco 监控运行时

监控工作负载和容器在运行时的安全违规行为至关重要。Falco 使集群用户能够及时应对严重的安全威胁和违规行为,或者捕捉那些绕过集群安全扫描和测试的安全问题。

Falco 是一个开源项目,最初由 Sysdig 开发,核心功能是 Kubernetes 中的威胁检测。它能够检测违规行为和异常行为的应用程序,并发送相关警报。你可以在这里了解更多关于 Falco 项目的信息:github.com/falcosecurity/falco

Falco 作为守护进程在 Kubernetes 的工作节点上运行,它在配置文件中定义了违规规则,你可以根据自己的安全需求进行自定义。

在你希望监控的工作节点上执行以下命令。这将安装并部署 Falco:

curl -o install_falco -s https://falco.org/script/install
sudo bash install_falco

为了自动化部署 Falco,我们将在本文件中使用 Terraform 将之前的命令包含到工作节点的引导用户数据中:terraform/modules/eks-workers/user-data.tf

Falco 可以检测到的安全运行时违规的一个示例是在容器内部启动 shell 时的检测。此违规的 Falco 规则如下所示:

- macro: container
  condition: container.id != host
- macro: spawned_process
  condition: evt.type = execve and evt.dir=<
- rule: run_shell_in_container
  desc: a shell was spawned by a non-shell program in a container. Container entrypoints are excluded.
  condition: container and proc.name = bash and spawned_process and proc.pname exists and not proc.pname in (bash, docker)
  output: "Shell spawned in a container other than entrypoint (user=%user.name container_id=%container.id container_name=%container.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)"
  priority: WARNING

你可以在你的 Falco 配置中使用和定义大量规则。要了解更多信息,请参考 Falco 文档和示例:falco.org/docs/examples/

确保集群安全和合规性

有许多影响 Kubernetes 集群安全的移动部件和配置。在部署安全附加组件并添加更多配置后,我们需要确保以下内容:

  • 集群安全配置有效完整

  • 根据互联网中心安全CIS)基准,集群符合标准安全指南

  • 集群通过 CNCF 及其合作伙伴和社区定义的一致性测试

在本节中,您将学习如何通过使用相关工具验证和保证上述每一点。

执行 Kubernetes 一致性测试

Kubernetes 社区和 CNCF 已定义了一组测试,您可以针对任何 Kubernetes 集群运行这些测试,以确保该集群在特定存储功能、性能测试、扩展测试、提供者测试以及由 CNCF 和 Kubernetes 社区定义的其他验证类型方面通过测试。这使得集群操作者可以信心满满地将其用于生产服务。

Sonobuoy 是一个工具,您可以使用它来运行这些一致性测试,并建议为新集群以及定期更新集群时执行。Sonobuoy 使您更容易确保集群状态,而无需影响其操作或造成任何停机时间。

安装 Sonobuoy

应用以下指令在本地主机上安装 Sonobuoy:

  1. 下载与您的操作系统匹配的最新 Sonobuoy 版本:github.com/vmware-tanzu/sonobuoy/releases

  2. 提取 Sonobuoy 二进制归档:

    $ tar -xvf <RELEASE_TARBALL_NAME>.tar.gz
    
  3. 将 Sonobuoy 二进制归档移动到您的bin文件夹或PATH系统的任何目录中。

运行 Sonobuoy

应用以下指令运行 Sonobuoy,然后查看一致性测试结果:

  1. 执行以下命令让 Sonobuoy 运行一致性测试,并等待其完成:

    $ sonobuoy run --wait --mode quick
    
  2. 要获取测试结果,请执行以下命令:

    $ sonobuoy_results=$(sonobuoy retrieve)
    $ sonobuoy results $sonobuoy_results
    
  3. 完成后,您可以删除 Sonobuoy,它将删除其命名空间和为测试目的创建的任何资源:

    $ sonobuoy delete --wait
    

为确保您的 Kubernetes 集群处于一致性状态,我们建议定期自动化执行 Sonobuoy 测试,可以每天或在基础设施部署和 Kubernetes 系统级更改后执行。我们不建议频繁和持续运行 Sonobuoy 测试,以避免给集群带来过多负荷。

扫描集群安全配置

完成集群一致性测试后,你需要扫描配置和安全设置,确保没有不安全或高风险的配置。为此,我们将使用kube-scan,它是一个安全扫描工具,用于扫描集群工作负载和运行时设置,并为每项打分,评分范围从 0(无风险)到 10(高风险)。kube-scan采用基于 Kubernetes 常见配置评分系统(CCSS)框架的评分公式。

安装 kube-scan

kube-scan通过以下kubectl命令作为 Kubernetes 部署安装在你的集群中:

$ kubectl apply -f https://raw.githubusercontent.com/octarinesec/kube-scan/master/kube-scan.yaml

kube-scan在启动时会扫描集群,并会每天定期扫描一次。这样,你可以通过重启kube-scan Pod 来强制重新扫描。

运行 kube-scan

按照以下指令运行kube-scan并查看扫描结果:

  1. 要访问kube-scan结果,你需要将kube-scan服务端口转发到本地机器的8080端口:

    $ kubectl port-forward --namespace kube-scan svc/kube-scan-ui 8080:80
    
  2. 然后,在浏览器中打开http://localhost:8080以查看扫描结果。

  3. 完成后,你可以使用以下kubectl命令删除kube-scan及其资源:

    $ kubectl delete -f https://raw.githubusercontent.com/octarinesec/kube-scan/master/kube-scan.yaml
    

我们建议将kube-scan部署到集群中,并自动化扫描结果验证,确保每天定期运行,或者在部署基础设施和 Kubernetes 系统级别更改后运行。我们不建议频繁且持续地运行 Sonobuoy 测试,以避免可能给集群带来的过大负载。

执行 CIS Kubernetes 基准

在集群的最终安全验证阶段,你应该测试集群是否按照 CIS 开发的 Kubernetes 基准进行部署和配置。

要执行此测试,你将使用kube-bench,它是一个用于运行 CIS Kubernetes 基准检查的工具。

重要说明

对于托管的 Kubernetes 服务(例如 EKS),由于无法访问主节点,因此无法使用kube-bench检查主节点。但是,仍然可以使用kube-bench检查工作节点。

安装 kube-bench

有多种方式安装kube-bench,其中一种是使用 Docker 容器将二进制文件和配置复制到主机。以下命令将进行安装:

$ docker run --rm -v `pwd`:/host aquasec/kube-bench:latest install

运行 kube-bench

在 Kubernetes 节点上执行kube-bench,并指定 Kubernetes 版本,例如 1.14 或任何其他支持的版本:

$ kube-bench node --version 1.14

你可以使用 CIS 基准版本,而不是指定 Kubernetes 版本,例如以下版本:

$ kube-bench node --benchmark cis-1.5

对于 EKS,你被允许运行以下特定目标:masternodeetcdpolicies

$ kube-bench --benchmark cis-1.5 run --targets master,node,etcd,policies

输出结果包括PASSFAIL,表示测试完成;WARN,表示测试需要人工干预;INFO是一个无需采取任何行动的提示性输出。

重要说明

我们建议自动化执行 Sonobuoy、kube-scankube-bench,以便每天验证集群的安全性和合规性。

启用审计日志

确保启用了集群审计日志,并且监控它们是否有异常或不需要的 API 调用,尤其是任何授权失败。对于 EKS,您需要选择启用这些日志,并将它们流式传输到 CloudWatch。

要启用此功能,您需要更新此文件 terraform/modules/eks-cp/main.tf 中的 Terraform EKS 资源,并添加以下代码行:

enabled_cluster_log_types = var.cluster_log_types

在将此 Terraform 更改应用于 EKS 配置后,集群审计日志将被流式传输到 CloudWatch,您可以从那里创建警报。

额外的安全建议

这些是一些通用的安全最佳实践和建议,它们没有适合前面任何章节的内容。然而,我认为它们很有用:

  1. 始终将 Kubernetes 更新到最新版本。

  2. 将工作节点的 AMI 更新到最新版本。您需要小心,因为此更改可能会引入一些停机时间,特别是当您没有使用托管节点组时。

  3. 不要在 Docker 中运行 Docker 或将套接字挂载到容器中。

  4. 限制使用 hostPath,或者如果 hostPath 必须使用,限制可使用的前缀并将卷配置为只读。

  5. 为每个容器设置请求和限制,以避免资源竞争和 拒绝服务DoS)攻击。

  6. 在可能的情况下,使用优化过的操作系统来运行容器。

  7. 使用不可变的基础设施,并自动化集群工作节点的轮换。

  8. 不应启用 Kubernetes 仪表板。

  9. 启用 AWS VPC 流日志,以捕获流经 VPC 的流量的元数据,然后进一步分析可疑活动。

Kubernetes 安全是一个快速发展的领域,您应该不断跟随最新的指南和最佳实践,并将它们集成到您的流程和 DevSecOps 自动化中。

部署安全配置

以下说明将部署集群的 Ansible playbook,它将向集群部署安全附加组件和配置:

  1. 通过运行以下命令初始化 Terraform 状态并选择工作区:

    $ cd terraform/packtclusters
    $ terraform workspace select prod1
    
  2. 获取并配置目标集群的 kubeconfig

    $ aws eks --region $(terraform output aws_region) update-kubeconfig --name $(terraform output cluster_full_name)
    
  3. 执行 Ansible playbook:

    $ source ~/ansible/bin/activate
    $ ansible-playbook -i \
    ../../ansible/inventories/packtclusters/ \
    -e "worker_iam_role_arn=$(terraform output worker_iam_role_arn) \
    cluster_name=$(terraform output cluster_full_name)
    aws_default_region=$(terraform output aws_region)" \
    ../../ansible/cluster.yaml
    
  4. 在成功执行 Ansible 后,您将得到以下输出:图 6.2 – Ansible 执行输出

    图 6.2 – Ansible 执行输出

  5. 执行以下 kubectl 命令以获取集群中运行的所有 pods。这将确保集群配置已成功应用:

    $ kubectl get pods --all-namespaces
    

    您应该得到以下输出,其中列出了集群中运行的所有 pods,包括用于安全附加组件的新 pods:

图 6.3 – 所有 pods 的列表

图 6.3 – 所有 pods 的列表

现在,您已经完成了按照之前的指示应用集群配置。您的集群已经部署了所有安全附加组件和配置,并准备好为生产环境提供服务。

销毁集群

首先,您应删除ingress-nginx服务,以指示 AWS 销毁与入口控制器相关联的 NLB。我们需要执行此步骤,因为 Terraform 会因 NLB 是由 Kubernetes 创建而无法销毁该 NLB:

$ kubectl -n nginx-ingress destroy svc nginx-ingress

然后,您可以按照第三章销毁网络和集群基础设施部分的其余指示,销毁 Kubernetes 集群和所有相关的 AWS 资源。请确保按以下顺序销毁这些资源:

  1. Kubernetes 集群packtclusters资源

  2. 集群 VPC 资源

  3. Terraform 共享状态资源

通过执行之前的步骤,您应该已经销毁并清理了所有 Kubernetes 和 AWS 基础设施资源,为下一个章节的实际操作做好准备。

总结

在本章中,您学习了 Kubernetes 安全最佳实践,并学习了如何将端到端的安全方法应用到集群的基础设施、网络、容器、应用、密钥、应用以及工作负载的运行时。您还学会了如何应用和验证安全合规性检查和测试。您开发了所有所需的模板和配置代码,用于这些最佳实践、控制器和附加组件,使用了 Ansible 和 Terraform。

您部署了 Kubernetes 附加组件和控制器,以提供诸如kube2iam、Cert-Manager、Sealed Secrets 和 Falco 等基本服务,并对 Kubernetes 原生的安全功能(如 Pod 安全策略、网络策略和 RBAC)进行了调优。

您在本章中已经掌握了 Kubernetes 安全的基本知识,但您应当详细评估您的集群安全需求,并采取进一步的措施,部署可能需要的额外工具和配置。

在下一章,您将详细了解 Kubernetes 的可观察性,以及监控和日志记录的最佳实践、工具、附加组件和配置,这些是您需要为生产级集群部署和优化的内容。

深入阅读

您可以参考以下链接,获取更多关于本章所涵盖主题的信息:

第七章:第七章:管理存储和有状态应用程序

在前几章中,我们学习了如何配置和准备 Kubernetes 集群以处理生产工作负载。在将应用程序和数据引入 Kubernetes 之前,配置并调优包括网络、安全、监控、日志、可观测性和扩展等零日任务是生产就绪的关键要求之一。Kubernetes 最初是为主要处理无状态应用程序而设计的,以确保容器的可移植性。因此,数据管理和运行有状态应用程序仍然是云原生领域中的主要挑战之一。有多种方法和不同的解决方案可以满足存储需求。新的解决方案每天都在 Kubernetes 和云原生生态系统中出现;因此,我们将从目前流行的生产解决方案开始,并学习评估未来解决方案时需要关注的方法和标准。

在本章中,我们将学习与 Kubernetes 上有状态应用程序相关的技术挑战。我们将完全遵循云原生的方法来调优 Kubernetes 集群,以实现持久存储。我们将了解不同的存储解决方案及其不足之处,以及如何使用和配置它们与我们的 Kubernetes 集群。

在本章中,我们将讨论以下主要主题:

  • 理解有状态应用程序的挑战

  • 调优 Kubernetes 存储

  • 选择持久存储解决方案

  • 部署有状态应用程序

技术要求

您应该已经安装了以下来自前几章的工具:

  • AWS CLI V2

  • AWS IAM 身份验证器

  • kubectl

我们还需要安装以下工具:

  • Helm

  • CSI 驱动程序

您需要按照第三章《使用 AWS 和 Terraform 配置 Kubernetes 集群》中的说明,确保您的 Kubernetes 集群已启动并运行。

本章的代码位于github.com/PacktPublishing/Kubernetes-Infrastructure-Best-Practices/tree/master/Chapter07

请查看以下链接观看《代码实战》视频:

bit.ly/3jemcot

安装所需的工具

在本节中,我们将安装在本章及接下来的章节中用于使用 Helm charts 部署应用程序并为 Kubernetes 基础设施中的有状态应用程序提供动态供应卷的工具。作为云计算和 Kubernetes 学习者,您可能已经熟悉这些工具。

安装 Helm

Helm 是 Kubernetes 的包管理器。Helm 也是在 Kubernetes 上查找和部署供应商及社区发布的应用程序的好方法。我们将使用 Helm 在我们的 Kubernetes 集群上部署应用程序。如果您的集群中没有安装 Helm,可以按照这些说明进行安装。

执行以下命令以在你的 Kubernetes 集群中安装 Helm 3:

$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh

接下来,我们将安装 CSI 驱动程序。

安装 CSI 驱动程序

容器存储接口CSI)是扩展 Kubernetes 的标准化 API,用于支持第三方存储提供商解决方案。CSI 驱动程序是供应商特定的,当然,如果你在 AWS 基础设施上运行(包括 EC2 或基于 EKS 的集群),你只需要 AWS EBS CSI 驱动程序。要安装最新的 AWS EBS CSI 驱动程序,请参考 Amazon EKS 官方文档,网址为 docs.aws.amazon.com/eks/latest/userguide/ebs-csi.htm

如果你正在使用自管理的 Kubernetes 解决方案、裸金属/本地部署或虚拟化环境,你可能需要使用其他供应商的 CSI 驱动程序或 容器附加存储CAS)解决方案。要安装其他 CSI 供应商的驱动程序,你可以参考官方 CSI 文档中的具体驱动程序说明,网址为 kubernetes-csi.github.io/docs/drivers.html

现在我们已经安装了本章中部署 Helm Charts 和使用 CSI 驱动程序访问 AWS EBS 卷所需的前提条件,让我们来回顾一下我们将遵循的实施原则,在做出存储提供商决策时,要着眼于解决有状态应用程序的挑战。

实现原则

第一章,《Kubernetes 基础设施与生产就绪介绍》中,我们学习了在本书中我们将遵循的基础设施设计原则。我想从本章开始时,强调影响本章云原生数据管理建议和技术决策的重要原则:

  • 简化:在本章中,我们将继续坚持简化原则。除非你在多云环境中操作,否则没有必要引入新工具来使操作复杂化。在公有云中,我们将使用提供的原生存储数据管理技术栈,这些技术栈由你的托管服务供应商支持。如今,许多有状态应用程序设计为能够容错并提供内建的高可用性功能。我们将识别不同类型的有状态应用程序,并学习如何简化数据路径并进行性能微调。我们还将学习额外的设计原则,以实现跨可用区的更高可用性,并统一本地和混合云环境中的数据管理。

  • 云无关性:数据具有引力。当运行无状态应用时,云厂商锁定可能不那么重要,因为容器镜像几乎可以在任何基础设施上即时启动,但当处理有状态工作负载时,就很容易进入这种情况。我们将使用云原生解决方案来抽象存储层并消除依赖。我们将实现的解决方案将在任何云提供商、托管 Kubernetes 服务,甚至是自主管理的本地环境中以相同方式工作。

  • 为高可用性设计:CSI 非常棒,但同时,它不过是标准化的 API。你的数据仍然需要存储在某个高可用的介质上。考虑存储解决方案的爆炸半径非常重要。将松散耦合的应用存储在单一的可扩展存储解决方案或传统存储设备上是没有意义的。这样做会造成规模瓶颈,最终会拖慢你的进程。我们将学习云原生存储解决方案的优势。我们还将学习如何使用快照、克隆和备份来提高服务可用性并快速恢复服务。

  • 自动化:除非所有内容都可以动态配置,否则你无法自动化你的 CI/CD 流水线。我们将学习 Kubernetes 存储原语以及动态配置器的使用。

在本节中,我们已经介绍了在做出存储提供商决策时将遵循的实施原则。现在让我们来看看一些常见的有状态应用挑战,我们需要解决这些问题。

理解有状态应用程序面临的挑战

Kubernetes 最初是为无状态应用构建的,以保持容器的可移植性。即使我们运行有状态应用,应用本身实际上也经常是无状态容器,状态存储在一个叫做持久卷PV)的资源中,并且是单独挂载的。我们将学习用于维护状态的不同资源类型,并且在稍后的理解 Kubernetes 中的存储原语部分中保持一定的灵活性。

我想强调在本章中我们将尝试解决的六个显著的有状态应用挑战:

  • 部署挑战:尤其是在生产环境中运行关键任务服务时,找到理想的有状态应用部署方法从一开始就可能具有挑战性。我们应该使用在博客文章、开源库示例、Helm charts 或操作器中找到的 YAML 文件吗?你的选择将影响未来的可扩展性、可管理性、升级和服务可恢复性。我们将在本章稍后的部署有状态应用程序部分中学习部署有状态应用程序的最佳实践。

  • 持久性挑战:存储使应用程序具有状态的实际持久数据需要仔细选择。你永远不应将状态存储在应用程序容器内部,因为容器镜像和 Pod 可以被重启和更新,这会导致数据丢失。同样,如果你在 EBS 卷上跨多个可用区运行集群,当某个节点重启时,应用程序可能会启动在一个位于不同可用区的节点上,而无法访问之前的 EBS 卷。在这种情况下,你应该考虑具有跨 可用区 (AZ) 复制功能的容器附加存储解决方案。

    另一方面,如果你的应用程序是具有内建高可用性的分布式数据库,来自存储提供商的额外高可用性层将对容量、成本和性能产生负面影响。持久性决策需要仔细考虑应用程序的需求。

  • 可扩展性挑战:Kubernetes 编排平台受欢迎的主要原因之一是它能够灵活扩展服务。Kubernetes 平台允许你从单个工作节点开始,根据需求和不断增加的负载动态扩展到数千个节点。并非每个存储解决方案都为扩展而设计。稍后在本章的 选择持久存储解决方案 部分中,我们将学习应遵循的最佳实践以及部署可扩展有状态应用程序时需要考虑的存储选项之间的差异。

  • 移动性挑战:数据移动性意味着能够在需要时获取数据,尤其是在要求使用混合云或多云架构的基础设施中,存储提供商的选择成为关键因素。这个需求也与我们在第一章中介绍的与云无关的设计原则相一致,Kubernetes 基础设施与生产就绪性简介。如果需要,你的有状态应用程序应该能够迁移到不同的区域,甚至不同的存储和云供应商。

  • 生命周期可管理性挑战:真正的挑战开始于你部署有状态应用程序之后。第二天的操作需要在生产服务之前提前规划好。这有时会对你的部署方法产生依赖和要求。你需要选择支持滚动升级、监控、可观察性和故障排除的部署方法。

  • 灾难恢复(DR)和备份挑战:你需要为应用程序和/或基础设施故障的情况下规划服务可用性。你的数据需要定期备份。一些应用程序可能要求应用一致的备份,而一些可能仅仅要求崩溃一致的备份。CSI 操作的快照和将数据复制到对象存储的操作需要进行调度。备份是问题的一方面,而能够及时从备份中恢复则是另一个挑战。当发生服务中断时,最终用户服务的影响通常通过两个数据点来衡量;恢复时间目标RTO)和恢复点目标RPO)。RTO 衡量恢复服务所需的时间,而 RPO 衡量备份频率。随着你的应用程序投入生产,数据可能会迅速增长。从类似 S3 的对象存储中恢复大量数据将需要时间。在这种情况下,需要考虑流备份解决方案。这个需求也与我在第一章《Kubernetes 基础设施与生产就绪性简介》中介绍的可用性设计设计原则相一致。如果需要,你的应用程序应该能够在最短的停机时间内快速切换到灾难恢复副本。

这六个核心挑战促成了我们在运行有状态应用程序时需要做出的架构设计决策。稍后在本章中,当我们评估存储选项并基于此做出相关技术决策时,我们将考虑这些挑战。

调优 Kubernetes 存储

在某些时候,我们都曾体验过存储性能的限制,并为此感到沮丧。在本章中,我们将学习 Kubernetes 存储的基础知识,包括存储原语、创建静态持久卷PVs)以及使用存储类来动态配置 PV,以简化管理。

理解容器化有状态应用程序要求我们进入云原生的思维方式。尽管被称为有状态,但 pods 使用的数据要么是远程访问的,要么是作为独立资源在 Kubernetes 中编排和存储的。因此,保持一定的灵活性,使得应用程序能够跨工作节点调度,并在需要时更新而不丢失数据。在我们开始调优之前,让我们先理解一些 Kubernetes 中的基本存储原语。

理解 Kubernetes 中的存储原语

Kubernetes 的魅力在于它的每个部分都被抽象为可以通过kube-api服务器使用 YAML 或 JSON 声明性地管理和配置的对象。这使得 Kubernetes 的配置更容易以代码的形式进行管理。存储也是作为抽象对象进行处理的。为了能够理解最佳实践背后的推理,我强烈建议你学习存储对象的分离。在本节中,我们将学习以下核心存储原语,以便从 Kubernetes 请求持久存储并通过与其相关的存储提供商进行编排:

  • 持久卷 (PV)

  • 持久卷声明 (PVC)

  • 存储类 (SC)

接下来我们将在以下部分讨论这些内容。

Kubernetes 卷基本上只是一个目录,供运行在 pod 中的容器中的应用程序访问。这个目录如何创建、保护,以及它存储在哪里,实际上取决于使用的卷类型,这使得这是在生产中运行有状态应用程序时的一个关键决策。Kubernetes 支持多种类型的卷。有关支持的卷类型的详细列表,请参考官方 Kubernetes 文档:kubernetes.io/docs/concepts/storage/volumes/。一些卷类型是临时的,换句话说,它们的生命周期仅限于其 pod。因此,它们应该仅用于无状态应用程序,这些应用程序的数据在重启后不需要持久化。在有状态应用程序的上下文中,我们关注的是 PV 类型,包括远程 PV 和本地 PV。现在让我们了解 PV 对象的使用。

PVs

PVs 是可以在 pod 重启或其他资源故障期间保持数据的卷。PVs 可以在预先静态创建,或者在用户应用程序请求时动态创建。我将通过一个实际示例来解释静态或动态 PV 对象的使用,同时我们部署一个 Percona 服务器。

重要说明

你可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter07/stateful/percona/pv-percona.yaml找到完整的源代码。

让我们从静态卷开始,了解它的限制,换句话说,理解动态供给背后的价值和逻辑:

  1. 创建一个 AWS Elastic Block Store 卷,大小为 100 GB,使用卷类型gp2。确保 EBS 卷与 Kubernetes 工作节点位于同一可用区:

    $ aws ec2 create-volume --size=10 --availability-zone=us-east-1a --volume-type=gp2
    
  2. 重复前一步骤,为集群中每个可用的工作节点创建一个卷。如果有三个节点可用,那么创建三个卷。执行以下命令以获取节点的InstanceId字符串列表:

    $ aws ec2 describe-instances | grep InstanceId
    
  3. 执行以下命令,使用 AWS CLI 将您创建的每个卷依次附加到集群中的一个工作节点。将WORKER_NODE_IDVOLUME_ID替换为第 1 步输出的内容:

    $ aws ec2 attach-volume --device /dev/sdf --instance-id <WORKER_NODE_ID> --volume-id <YOUR_VOLUME_ID>
    
  4. 在以下路径stateful/percona/pv-percona.yaml中创建一个名为percona-pv1,大小为5Gi的 Kubernetes PV。确保将volumeID替换为您的 EBS 卷的有效卷 ID:

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name : percona-pv1
    spec:
      accessModes:
      - ReadWriteOnce
      capacity:
        storage: 5Gi
      persistentVolumeReclaimPolicy: Retain
      awsElasticBlockStore:
        volumeID: <YOUR EBS VOLUME ID HERE>
        fsType: xfs
    
  5. 执行以下kubectl命令以在集群中创建静态 PV:

    $ kubectl apply -f pv-percona.yaml
    

现在您已经创建了一个可以绑定到有状态应用程序的 PV。如您所见,如果您有一个动态扩展的环境,提前手动创建卷将无法提供可扩展的选项。

PV 声明

PV 声明PVC)是对存储的请求。PVC 请求可以通过静态或动态的 PV 来满足。

重要说明

您可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter07/stateful/percona/pvc-percona.yaml找到完整的源代码。

在这里,我们将创建一个 PVC 清单,以请求我们之前创建的静态 PV:

  1. 创建一个名为percona-pv1,大小为5Gi的 PVC,路径为stateful/percona/pvc-percona.yaml

    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: percona-pvc
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 5Gi
    
  2. 在模板的以下部分,我们将storageClassName设置为空白。否则,将使用默认的存储类,并且会使用默认的存储提供程序动态创建 PV。这一次,我们特意请求一个没有指定存储类的 PV,因此它只能绑定到我们现有的 PV:

      storageClassName: ""
    
  3. 执行以下kubectl命令以在集群中创建 PVC 对象:

    $ kubectl apply -f pv-percona.yaml
    

    重要说明

    您可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter07/stateful/percona/deployment-percona.yaml找到完整的源代码。

在以下代码片段中,您将创建percona部署,该部署将使用 PVC 请求我们之前创建的 PV:

  1. 执行以下命令创建一个 Kubernetes 密钥以保存 Percona 的 root 密码。这个密钥将在稍后的部署中使用。您可以在kubernetes.io/docs/concepts/configuration/secret/中阅读有关 Kubernetes 密钥的详细用法:

    $ kubectl create secret generic mysql-root \
         --from-literal=mysql-root-passwd=MyP@ssW0rcl \
         --dry-run -o yaml | kubectl apply -f -
    
  2. 在以下路径stateful/percona/deployment-percona.yaml中创建percona部署的模板:

    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: percona
    spec:
      selector:
        matchLabels:
          app: percona
      template:
        metadata:
          labels:
            app: percona
        spec:
          containers:
          - image: percona
            name: percona
            env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-root
                  key: mysql-root-passwd
            ports:
            - containerPort: 3306
              name: percona
    
  3. 在模板的以下部分,我们将使用名称percona-volume定义volumeMounts,并将mountPath参数配置为路径/var/lib/mysql,这是您的 PV 将在容器内挂载的路径:

            volumeMounts:
            - name: percona-volume
              mountPath: /var/lib/mysql
    
  4. 最后,在模板的以下部分,我们将定义您的请求将被指向的位置。就我们而言,正如之前在claimName的案例中定义的,这应该是percona-pvc

         volumes:
            - name: percona-volume
              persistentVolumeClaim:
                claimName: percona-pvc
    
  5. 执行以下kubectl命令,以在集群中创建percona部署:

    $ kubectl apply -f deployment-percona.yaml
    

现在,您已经创建了一个有状态应用程序部署,并绑定到一个静态 PV。虽然了解如何克隆现有卷并将其挂载到新 Pod 可能会有用,但这并不是一个可扩展的解决方案。因此,我们现在将学习如何使用StorageClass进行 PV 的动态配置。

存储类

StorageClass对象允许通过 PVC 进行动态配置请求。您可以维护多个类,这些类映射到不同的可用性和 QoS 级别,使用内部或外部第三方供应者。StorageClass概念类似于传统存储解决方案中的层级或配置文件。

重要说明

您可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter07/stateful/percona/deployment-percona.yaml找到完整的源代码。

让我们回顾一下用于在 AWS 上配置 EBS 卷的StorageClass模板:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp2

在模板的以下部分,我们将StorageClass设置为默认存储类。强烈推荐将默认存储类设置为最佳实践,这样如果 PVC 缺少storageClassName字段,它会自动分配到您的默认类:

  annotations:
    storageclass.kubernetes.io/is-default-class: "true"

在模板的以下部分,我们将 EBS 卷类型设置为gp2,并使用 AWS EBS 卷类型io1gp2sc1st1。您可以在官方 AWS 文档中阅读有关这些类型的差异,网址为docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html。我们还将fsType设置为ext4

parameters:
  type: gp2
  fsType: ext4

在模板的以下部分,我们将provisioner类型设置为kubernetes.io/aws-ebs。此字段可以是内部或外部供应者。在我们接下来的模板中,它设置为 Kubernetes 的内部aws-ebs供应者kubernetes.io/aws-ebs。我们将在本章稍后选择持久存储解决方案部分中回顾可用的存储选项:

provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: Immediate

reclaimPolicy可以设置为DeleteRecycleRetain,它定义了当相应的 PVC 被删除时的操作。当选择Retain时,PVC 被移除后,PV 将转移到Released状态。因此,建议选择Retain以避免意外发生。

allowVolumeExpansion字段用于在稍后需要请求更大大小的 PVC 时,并希望调整相同卷的大小,而不是获取新的卷。只有当存储类的allowVolumeExpansion参数设置为true时,才能扩展 PVC。

注意

AWS EBS 卷扩展可能需要时间,并且每 6 小时允许进行一次修改。

volumeBindingMode可以设置为ImmediateWaitForFirstConsumer。此参数规定了何时进行卷绑定。

要了解更多StorageClass参数的信息,请查看 Kubernetes 官方文档:kubernetes.io/docs/concepts/storage/storage-classes/

重要提示

您可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter07/stateful/percona/deployment-percona-sc.yaml找到完整的源代码。

现在,我们将修改pvc-percona.yamldeployment-percona.yaml清单文件。我们将调整percona部署,使其通过 PVC 动态请求 PV,并使用存储类:

  1. 使用您喜欢的文本编辑器编辑此路径下的percona-pvc PVC 模板:stateful/percona/pvc-percona.yaml,并根据以下内容调整namestorageClassName字段:

    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: percona-pvc-gp2
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 5Gi
      storageClassName: gp2
    
  2. 使用您喜欢的文本编辑器编辑此路径下的percona部署模板:stateful/percona/deployment-percona.yaml,并根据以下内容调整最后一行的claimName字段:

                claimName: percona-pvc-gp2
    
  3. 执行以下kubectl命令,在集群中使用动态配置的 PV 创建percona部署:

    $ kubectl apply -f pv-percona.yaml
    $ kubectl apply -f deployment-percona.yaml
    

现在,您已经创建了一个与动态配置的 PV 绑定的有状态应用部署,使用了StorageClass。这个步骤完全消除了手动创建 EBS 卷的需求。因此,在本章稍后创建新的有状态应用时,我们将使用这种方法。

选择一个持久存储解决方案

在 Kubernetes 中,最大的两个有状态应用挑战是存储编排和数据管理。市场上有无数解决方案。首先,我们将解释在评估存储替代方案时需要考虑的主要存储属性和拓扑结构。让我们回顾一下最常见存储系统所使用的拓扑结构:

  • 集中式:传统的,也称为单体存储系统,通常与专有硬件和内部通信协议紧密耦合。它们通常与向上扩展的模型相关,因为难以扩展紧密耦合的存储节点组件。

  • 分布式:分布式存储系统更可能是软件定义的解决方案,它们的架构可能会偏向可用性、一致性、耐久性、性能或可扩展性。通常,分布式系统比其他系统更能支持多个存储服务器节点并行扩展。

  • 超融合:超融合存储解决方案旨在利用应用程序运行所在的相同网络和计算资源。它们主要设计为软件运行,并由与应用程序、虚拟机或容器管理相同的平台进行编排,如虚拟化管理程序或容器编排器。

  • 分片:分片存储解决方案将数据分割成多个数据集,并将它们分布存储在多个节点上。分片存储解决方案可能管理起来比较复杂,需要重新平衡,而且性能受限于数据集所在单个节点的性能。

云原生应用可用的存储解决方案类别被云原生计算基金会CNCF)定义为云原生存储。目前,列出了 17 个开源解决方案和 32 个专有解决方案,总共 49 个解决方案。

如需查看最新的解决方案列表,请参考官方的 CNCF 云原生互动生态文档:landscape.cncf.io/

图 7.1 – CNCF 云原生生态图,包含云原生存储提供商

图 7.1 – CNCF 云原生生态图,包含云原生存储提供商

考虑到理解有状态应用面临的挑战部分提到的挑战,针对块存储的简化部署和生命周期管理,容器附加存储CAS)和云存储优于集中式拓扑。为了满足跨不同基础设施和数据流动性的持久性要求,应该优先选择CAS分布式解决方案,而非右侧的解决方案。在讨论 Kubernetes 级别的可扩展性时,云存储CAS解决方案也显著优于集中式拓扑。总体而言,CAS云存储提供商能够满足所有架构需求。尽管如此,在许多情况下,我们仍然需要利用公司现有的投资。云存储仅在云服务商提供的基础设施上可用,如果你是在本地/私有云环境中运行,可能需要利用现有的硬件解决方案。在这种情况下,你仍然可以利用CAS解决方案来统一数据管理,增加云原生存储的优势,包括数据流动性和可扩展性,并简化 PV 生命周期管理。

现在你已经了解了最常见存储解决方案使用的存储拓扑结构,接下来我们来关注如何使用 CAS 解决方案来部署有状态应用。

部署有状态应用

Kubernetes 提供了多个控制器 API 来管理 Kubernetes 集群中 Pod 的部署。这些控制器最初是为无状态应用程序设计的,旨在根据需求将 Pod 分组。在本节中,我们将简要了解以下 Kubernetes 对象的区别——Pod、ReplicaSets、Deployments 和 StatefulSets。如果节点发生故障,个别 Pod 不会被重新调度到其他节点。因此,在运行有状态工作负载时,应避免使用这些对象。

部署 用于管理 Pod,而 副本集 用于在需要对副本 Pod 进行更新时。副本集和部署都用于配置无状态应用程序。要了解更多关于部署的信息,请查阅官方 Kubernetes 文档:kubernetes.io/docs/concepts/workloads/controllers/deployment/

StatefulSets 是另一种控制器,在 Kubernetes 1.9 发布时达到了 正式发布GA)的里程碑。随着 StatefulSets 对象的引入,有状态应用程序的真正采用开始了。通过 StatefulSets,每个 Pod 副本都有自己的状态,换句话说,就是它自己的卷,因此在重启后仍能保持其状态和身份。在部署有状态应用程序时,当我们需要存储保持状态时,我们将使用 StatefulSets。以下图示显示了使用 StatefulSets 部署的应用程序组件:

图 7.2 – Kubernetes StatefulSet 部署示意图

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_07_002.jpg)

图 7.2 – Kubernetes StatefulSet 部署示意图

StatefulSets 需要一个无头服务来处理相关 Pod 的网络身份。当 StatefulSet 请求创建卷时,它使用 StorageClass 来调用 PV 配置器。在本章前面,你已经学习了如何使用 StorageClass 动态配置 PV。

在我们部署有状态应用程序之前,我们将学习如何安装一个流行的开源存储配置器选项——OpenEBS,这也是我们在 选择持久化存储解决方案 部分提到过的。

安装 OpenEBS

OpenEBS 是一个开源的 CNCF 项目,旨在使 Kubernetes 中的有状态应用程序能够轻松访问动态本地 PV,或复制的高可用 PV。OpenEBS 是一种新的云原生存储解决方案类别(称为 CAS)的例子。CAS 解决方案易于维护,具有可移植性,能在任何平台上运行,具备可扩展性,并且符合我在 第一章 Kubernetes 基础设施和生产就绪性介绍 中介绍的基础设施设计原则。

要了解有关 OpenEBS 的更多先决条件和详细用法,请参考以下链接:docs.openebs.io/

现在,让我们在 Kubernetes 集群上安装 OpenEBS,并为您的集群准备动态供应的 PV:

  1. 创建一个名为 openebs 的命名空间:

    $ kubectl create ns openebs
    
  2. 将 OpenEBS Helm chart 仓库添加到本地仓库列表中:

    $ helm repo add openebs https://openebs.github.io/charts
    
  3. 更新 Helm chart 仓库:

    $ helm repo update
    
  4. 从 Helm 仓库安装 openebs

    $ helm install --namespace openebs openebs openebs/openebs
    
  5. 通过执行以下命令验证安装是否成功:

    $ kubectl get pods -n openebs
    
  6. 上述命令的输出应如下所示:

图 7.3 – 安装成功后运行的 OpenEBS Pods 列表

图 7.3 – 安装成功后运行的 OpenEBS Pods 列表

既然您可以使用 OpenEBS 动态创建 PV,您可以创建一个新的 SC 或使用 OpenEBS 提供的默认存储类之一。

OpenEBS 提供了各种类型的块存储选项,包括名为 JivacStorMayastor 的存储引擎,适用于需要在节点故障期间保证高可用卷的持久化工作负载,以及适用于分布式应用程序(如 Cassandra、Elastic、Kafka 或 MinIO)的 Dynamic Local PV(设备、主机路径、ZFS)替代方案。

执行以下命令以获取集群中默认存储类的列表:

$ kubectl get sc

您将注意到新添加到列表中的存储类:openebs-deviceopenebs-hostpathopenebs-jiva-defaultopenebs-snapshot-promoter

这是一个 YAML 清单示例,用于使用默认的 openebs-jiva-default 存储类创建 PVC:

---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: openebs-pvc
spec:
  storageClassName: openebs-jiva-default
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5G
---

现在您已经学习了如何使用开源 CAS 替代方案 OpenEBS 为有状态应用程序创建 PV。

从现在开始,如果运行在 AWS 基础设施上,您可以继续使用现有的 EBS 卷,使用 gp2 存储类或先前通过 Amazon_EBS_CSI_Driver 创建的 ebs-sc 存储类,或者利用 OpenEBS 来抽象化数据管理。与 CAS 解决方案相似,OpenEBS 帮助减少了我们在本章 理解有状态应用程序的挑战 部分中描述的许多挑战。

现在我们已经学习了如何使用存储提供者动态提供 PV,让我们将其与有状态应用程序一起使用,以简化数据管理的生命周期。

在 OpenEBS 卷上部署有状态应用程序

OpenEBS 提供了一个灵活的数据平面,拥有多个存储引擎选项,针对不同的应用和性能需求进行了优化。您可以在官方 OpenEBS 文档网站 docs.openebs.io/docs/next/casengines.html 阅读有关存储引擎差异的详细信息。在这里,我们将深入了解其中一个默认选项,即低占用存储引擎选项 Jiva

接下来,我们将修改 pvc-percona.yamldeployment-percona.yaml 清单文件。我们将调整 percona 部署,以便使用 StorageClass 动态请求通过 PVC 创建 PV:

  1. 在以下路径 stateful/percona/sc-openebs-jiva.yaml 中创建一个名为 openebs-jiva-3rStorageClass,并设置 ReplicaCount3。这将创建三个副本的卷,并在节点故障时提供高可用性:

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: openebs-jiva-3r
      annotations:
        openebs.io/cas-type: jiva
        cas.openebs.io/config: |
          - name: ReplicaCount
            value: "3"
          - name: StoragePool
            value: default
    provisioner: openebs.io/provisioner-iscsi
    
  2. 执行以下 kubectl 命令创建 StorageClass:

    $ kubectl apply -f sc-openebs-jiva.yaml
    
  3. 使用您喜欢的文本编辑器编辑此路径下的 percona-pvc PVC 模板,stateful/percona/pvc-percona.yaml。调整名称和 storageClassName 字段,如下所示:

      storageClassName: openebs-jiva-3r
    
  4. 使用您喜欢的文本编辑器编辑此路径下的 percona 部署模板,stateful/percona/deployment-percona.yaml。调整最后一行 claimName,如下所示:

                claimName: percona-pvc-openebs
    
  5. 执行以下 kubectl 命令,在集群中使用动态供应的 PV 创建 percona 部署:

    $ kubectl apply -f pvc-percona.yaml
    $ kubectl apply -f deployment-percona.yaml
    

现在,您已经创建了一个由动态创建的 OpenEBS PVs 支持的有状态应用程序部署。这一步帮助我们在云端、裸金属或基于虚拟机的 Kubernetes 集群中抽象了数据管理。

总结

在本章中,我们了解了有状态应用程序的挑战,并讨论了在选择最佳存储管理解决方案时应考虑的最佳实践,包括开源和商业解决方案。最后,还介绍了在使用 Kubernetes 的 StatefulSet 和部署对象将其部署到生产环境时需要考虑的有状态应用程序问题。

我们部署了 AWS EBS CSI 驱动程序和 OpenEBS,并使用 OpenEBS 创建了高度可用的复制存储,并将我们的应用程序部署在 OpenEBS 卷上。

我们在本章中深入了解了 Kubernetes 存储,但您应对您的集群存储需求进行详细评估,并采取进一步措施,部署任何可能需要的额外工具和配置,包括存储提供商的 CSI 驱动程序。

在下一章中,我们将详细了解无缝且可靠的应用程序。我们还将掌握容器化的最佳实践,以便轻松地扩展我们的应用程序。

深入阅读

您可以参考以下链接,了解更多关于本章所涵盖主题的信息:

第八章:第八章:部署无缝且可靠的应用程序

在前面的章节中,我们学习了如何为生产使用准备平台和基础设施组件。我们还了解了 Kubernetes 数据管理的注意事项以及存储最佳实践,以使用 Operator Framework 部署我们的第一个有状态应用程序。容器编排中最被低估的话题之一是容器镜像管理。尽管在本书中不涉及在 Kubernetes 中开发应用程序,但我们需要了解镜像的关键组件。我们可以通过多个来源、公共容器注册表和供应商来找到现成的应用程序镜像。错误处理容器镜像不仅可能导致集群资源的过度使用,更重要的是,还可能影响我们服务的可靠性和安全性。

本章我们将讨论容器和镜像管理等话题。我们将了解在选择或创建影响 Kubernetes 集群稳定性和安全性的应用程序镜像时遇到的技术挑战。我们将重点关注在将生产服务部署到集群之前,如何在应用程序发布中采用最佳实践,以避免集群不稳定或误用。这将帮助我们充分利用 Kubernetes 安全地编排我们的服务。

本章我们将涵盖以下主要内容:

  • 理解容器镜像面临的挑战

  • 学习应用程序部署策略

  • 扩展应用程序并实现更高的可用性

技术要求

你应该已经安装了前几章中提到的以下工具:

  • kubectl

  • metrics-server

你需要根据 第三章《使用 AWS 和 Terraform 配置 Kubernetes 集群》中的说明,拥有一个正在运行的 Kubernetes 集群。

本章的代码位于 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter08

查看以下链接,观看“代码实战”视频:

bit.ly/3rpWeRN

理解容器镜像面临的挑战

本节中,我们将了解行业专家在构建或选择正确的容器镜像时的注意事项和最佳实践。在讨论挑战并深入探讨我们的选择之前,让我们了解容器镜像的组成。

探索容器镜像的组成

要理解容器镜像的行为,我们需要具备基本的操作系统OS)和层级保护域的知识。为了安全隔离,操作系统将虚拟内存分为两层,称为内核空间用户空间。基本上,内核运行在最特权的保护环中,叫做环 0,并直接与诸如 CPU 和内存等关键资源进行交互。内核需要保持稳定,因为任何问题或不稳定都会导致整个系统的不稳定,进而使系统进入崩溃状态。正如我们在图 8.1中看到的,驱动程序、低级系统组件以及所有用户应用程序都运行在最不特权的保护环和用户空间中:

图 8.1 – 特权环,也称为层级保护域

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_08_001.jpg)

图 8.1 – 特权环,也称为层级保护域

若要了解用户空间的详细信息,请查看这里的详细解释:debian-handbook.info/browse/stable/sect.user-space.html

Linux 容器在安全隔离方面更进一步,使我们能够在所谓的容器主机容器镜像中分别管理应用程序和操作系统的依赖项。

容器主机是操作系统与容器运行时(一些流行的容器运行时包括containerdCRI-OFirecrackerKata)以及容器引擎(一些流行的容器引擎包括 Docker 和 Linux 容器守护进程LXD))一起运行的地方。在本书中,我们不会讨论容器运行时和引擎之间的区别,因为它们大多数时候是平台的一部分,这不在我们的讨论范围内。在传统的单体架构中,我们在操作系统上运行应用程序及其操作系统依赖项和其他应用程序,而在云原生微服务架构中,我们将应用程序及其依赖项运行在容器镜像中(见图 8.2):

图 8.2 – 单体架构与微服务架构的比较

图 8.2 – 单体架构与微服务架构的比较

当我们在 Kubernetes 中运行应用程序时,例如 NGINX、Cassandra、Kafka、MongoDB 等,我们的容器引擎会从容器注册中心将容器镜像拉取到本地注册中心,然后将一个或多个容器封装成一个叫做pod的对象,并将其调度到可用的工作节点上。

在这个过程中使用的容器镜像(大多数时候,这个术语被误用为基础镜像)是一个分层镜像,包含了用户应用程序和容器基础镜像。

容器基础镜像包含操作系统的可交换用户空间组件。容器镜像遵循 Docker 镜像或开放容器倡议OCI)的行业标准进行打包。这就是我们面临的选择和挑战。大多数容器基础镜像包含具有操作系统分发版最小用户空间应用程序的根文件系统,以及其他一些外部库、实用工具和文件。容器镜像通常用于软件开发,并提供一个用常见编程语言编写的功能性应用程序。编程语言,包括编译型和解释型语言,依赖于外部驱动程序和库。这些依赖关系使得容器基础镜像的选择至关重要。

在我们构建应用程序或在生产环境中运行基于现有镜像的应用程序之前,我们需要理解流行的容器基础镜像之间的关键区别。现在你已经了解了容器镜像的构成,让我们来学习常见的容器基础镜像之间的区别,以及选择正确镜像类型的一些最佳实践。

选择正确的容器基础镜像

选择容器基础镜像与选择容器主机的 Linux 发行版没有太大区别。需要考虑的类似标准包括安全性、性能、依赖性、核心实用工具、包管理器、社区和生态系统的规模,以及安全响应和支持等。

我想重点介绍在本章中我们将尝试解决的五个显著的容器镜像挑战:

  • 镜像大小:容器镜像的一个重要优势是便携性。较小的容器镜像大小减少了构建和发布的时间,因为拉取镜像本身会更快。通过限制额外的二进制文件来实现更小的镜像,这也带来了最小化的攻击面和增强的安全性优势。

  • 稳定性:更新基础镜像并不有趣,但更新每一个容器镜像则是最糟糕的。仅包含你的应用程序及其运行时依赖项的容器镜像,如 distroless 镜像,可能听起来很有吸引力。然而,在修补常见漏洞和暴露CVE)时,你需要更新所有容器,这可能会引入稳定性问题。

    重要提示

    Distroless 镜像是没有包管理器或任何其他应用程序的容器镜像。你可以在这里阅读更多关于 distroless Docker 镜像的内容并观看演讲:github.com/GoogleContainerTools/distroless

  • 安全性: 每添加到容器镜像中的二进制文件都会为整体平台安全性带来不可预测的风险。在选择基础镜像时,其更新频率、生态系统和社区规模以及漏洞跟踪方法(如 CVE 数据库和 开放漏洞评估语言 (OVAL) 数据)是需要考虑的重要因素。检查可执行文件的属性,如 位置独立执行文件 (PIE)、只读重定位 (RELRO)、Linux 内核补丁 (PaX)、金丝雀、地址空间布局随机化 (ASLR)、FORTIFY_SOURCE 以及 RPATH 和 RUNPATH 运行时搜索路径。

    重要提示

    你可以在这里找到检查二进制硬化工具属性的 Bash 脚本:github.com/slimm609/checksec.sh

  • 速度/性能: 流行的容器基础镜像不一定是最快的。虽然 Alpine 以其小巧著称,并且在某些情况下推荐使用,但它可能会导致严重的构建性能问题。如果你使用 Go 语言,Alpine 可能是可以接受的。然而,如果使用 Python,你会很快注意到 Alpine 镜像有时会变得大两到三倍,构建速度比普通镜像慢 10 倍以上,甚至可能导致构建问题。

    重要提示

    你可以在这里找到与 Kubernetes 相关的性能测试工具:github.com/kubernetes/perf-tests

  • glibc,Alpine 包含 muslc 并可以显示实现差异。此外,镜像中包含的故障排除和支持工具也需要考虑。

以下是一些常见容器基础镜像选项,按其大小、安全性和支持选项进行比较:

  • 带有包管理器的 busybox。已知 glibc/musl 库的差异会引起难以追踪的问题和性能问题:

    • 大小: 2.6 MB。

    • 安全性: 社区更新;Alpine Linux 错误跟踪器可以在 bugs.alpinelinux.org/projects/alpine/issues 访问。

    • 支持: 通过社区提供支持。支持 386、AMD64、ARMv6、ARMv7、ARM64v8、ppc64le 和 S390x 架构。

  • Amazon Linux 2 (amazonlinux:2): 由 亚马逊 Web 服务 (AWS) 维护的 Linux 镜像,用于在 Amazon EC2 实例上使用。与 RHEL 和 CentOS 二进制兼容:

    • 大小: 59.14 MB。

    • 安全性: 厂商更新;Amazon Linux 安全中心可以在 alas.aws.amazon.com/alas2.html 访问。

    • 支持: 包括 AWS EC2 的 LTS 支持;支持 AMD64 和 ARM64v8 架构。

  • CentOS (centos:8): 由社区驱动的流行 Linux 发行版的容器基础镜像。由于 CentOS Stream 的推出,其未来尚不明确。目前,最好等待替代的 Rocky Linux 基础镜像,或使用 Amazon Linux 2:

    • 大小: 71.7 MB。

    • 安全性:由社区更新;可以在这里找到 CentOS 的安全警报:lwn.net/Alerts/CentOS/

    • 支持:仅通过社区提供支持。支持 AMD64、ARM64v8 和 ppc64le 架构。

  • libc)包含在 Debian 镜像中:

  • Ubuntu (ubuntu:21.04):基于 Debian 的大型社区和企业支持的 Linux 发行版基础镜像:

  • microdnf作为包管理器。它在运行ubi-minimal)、标准(ubi)和多服务(ubi-init)时被首选,用于不同的使用场景:

    • 大小:37.6 MB。

    • 安全性:在漏洞检查的完整性方面,是最好的容器基础镜像。在access.redhat.com/errata提供 Errata,并在www.redhat.com/security/data/oval/提供 OVAL 数据。

    • 支持:社区和商业支持。支持 AMD64、ARM64v8、ppc64le 和 S390x 架构。

  • Distroless (gcr.io/distroless/base-debian10):由 Google 在 Debian 发行版基础上构建。它们不包含包管理器或 shell。因其安全性和小巧性而受青睐。更多构建可以在console.cloud.google.com/gcr/images/distroless/GLOBAL找到:

    • 大小:75.1 MB

    • 安全性:避免了镜像的漏洞,但引入了另一个挑战,即每个容器镜像的依赖库更新需要仔细跟踪。

    • 支持:仅通过社区提供支持。支持 AMD64、ARM、ARM64、ppc64le 和 S390x 架构。

现在你已经了解了选择正确的容器基础镜像时所面临的挑战,以及最常见的流行基础镜像的对比。接下来,让我们探讨一些减少最终镜像大小和扫描容器镜像漏洞的最佳实践。

减少容器镜像大小

实现更小容器镜像的一个优秀方法是从小型基础镜像开始,如 Alpine、ubi-minimal或 distroless 基础镜像。

注意

为了实现可复现的构建和部署,你还可以使用 Nix 包管理器来创建精简的构建。Nix 受到很多热情的关注,但由于其陡峭的学习曲线和自定义表达语言的使用,我们在本书中不会讨论 Nix。你可以在官方 NixOS 文档页面了解关于使用 Nix 构建容器镜像的内容:nixos.org/guides/building-and-running-docker-images.html

排除一些不必要的文件,使用 .dockerignore 文件可以帮助我们减少镜像的大小。以下是一个 .dockerignore 文件的示例:

# ignoring git folder
.git
#ignoring visual studio code related temp data
.vs
.vscode
# other files and CI manifests
.DS_Store
.dockerignore
.editorconfig
.gitignore
.gitlab-ci.yml
.travis.yml
# ignore all files and directories starting with temp 
# in any subdirectory 
*/temp*
# ignore all files and directories starting with temp
# in any subdirectory two levels below root
*/*/temp*
# ignore all files and directories starting with temp
# followed by any character 
temp? 

通过利用多阶段构建并避免额外的层,可以实现优化大小的镜像。多阶段构建增加了一些新的语法,允许我们在 Dockerfile 中多次使用FROM部分来启动构建的新阶段,并且只复制我们希望从之前阶段获取的工件。你可以在官方 Docker 文档网站上了解更多关于多阶段构建的信息:docs.docker.com/develop/develop-images/multistage-build/

下面是一个包含两个阶段的 Dockerfile 示例:

FROM node:14.15 AS base
ADD . /app
WORKDIR /app
RUN npm install
FROM gcr.io/distroless/nodejs AS stage2
COPY --from=base /app /app
WORKDIR /app
EXPOSE 8080
CMD ["server.js"]

在我们之前的示例中,第一个阶段 base 使用 node:14.15 的 Node.js 基础镜像。我们将应用程序代码复制到 /app 目录并执行 npm install 命令。

接下来我们进入第二阶段,称为 stage2,这次使用 distroless/nodejs 基础镜像。然后,我们使用 COPY --from=base /app /app 语法从第一个阶段复制应用程序代码和 node_modules。通过这种方式,我们既减少了容器镜像的大小,又减少了攻击面,因为 distroless 镜像不包含 bash 或其他可能被恶意执行的工具。

你可以在docs.docker.com/develop/develop-images/dockerfile_best-practices/阅读编写 Dockerfile 的最佳实践。

现在我们已经学习了一些减少容器镜像大小的技巧。接下来,看看我们如何主动扫描镜像中的安全漏洞,并在将其投入生产环境之前及时修复。

扫描容器镜像中的漏洞

我们已经构建了容器镜像或从供应商提供的镜像中拉取了一些到本地注册表,现在我们准备在生产环境中运行它们。我们如何知道它们是安全的?我们如何知道它们已经修补了最新的安全漏洞?如今,大多数持续集成(CI)持续交付(CD)解决方案都配有额外的安全扫描工具。一个黄金法则是在将任何服务投入生产之前,一定要通过管道中的快速镜像验证。为此,我们将学习一个流行的开源解决方案,叫做Trivy

Trivy 是一个全面的容器镜像漏洞扫描工具。Trivy 能够检测大多数基于流行基础镜像的漏洞,包括 Alpine、CentOS 和 Red Hat UBI,以及如 npmyarnbundlercomposer 等应用程序包的依赖关系。

在这里,我们将手动安装 trivy 二进制文件并进行漏洞分析:

  1. 让我们获取 trivy 的最新版本标签,并将其保存在名为 TRIVYVERSION 的变量中:

    $ TRIVYVERSION=$(curl –silent "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name":' | \
    sed -E 's/.*"v([^"]+)".*/\1/')
    
  2. 现在,下载最新的 trivy 二进制文件并进行安装:

    $ curl --silent --location "https://github.com/aquasecurity/trivy/releases/download/v${TRIVYVERSION}/trivy_${TRIVYVERSION}_Linux-64bit.tar.gz" | tar xz -C /tmp
    $ sudo mv /tmp/trivy /usr/local/bin
    
  3. 通过执行以下命令确认安装已成功完成:

    $ trivy --version
    Version: 0.14.0
    
  4. 运行 trivy 检查时,需要指定目标镜像位置及其标签。在我们的示例中,我们扫描了来自官方 Docker Hub 仓库的 alpine:3.12 基础镜像:

    $ trivy alpine:3.12
    

    由于在特定容器镜像中未发现任何问题,前述命令的输出应如下所示:

    图 8.3 – 没有已知漏洞的容器镜像的 Trivy 扫描结果

    图 8.3 – 没有已知漏洞的容器镜像的 Trivy 扫描结果

  5. 现在,让我们扫描一个公开的流行 MongoDB 数据库容器镜像。MongoDB 被许多现代云原生应用和服务使用:

    $ trivy mongo:4.4
    
  6. 你会注意到,Trivy 返回了93个已知漏洞,其中包括2个高危和28个中危问题:

图 8.4 – 显示漏洞的 Trivy 扫描结果

图 8.4 – 显示漏洞的 Trivy 扫描结果

在 Trivy 扫描器返回的详细分析中,你可以找到漏洞 ID 和严重性 URL,以便了解更多问题。你还可以看到一些问题源自容器镜像中使用的 Ubuntu 18.04 基础镜像,并且可以通过仅更新容器的基础镜像来解决。

Trivy 支持大多数 CI 工具,包括 Travis CI、CircleCI、Jenkins 和 GitLab CI。如需了解更多关于 Trivy 及其集成的详细信息,可以阅读官方文档:github.com/aquasecurity/trivy

现在我们已经学习了如何测试容器镜像中的已知漏洞。强烈建议在构建流水线中设置测试条件。接下来,让我们看看如何测试从公共仓库下载容器镜像的影响。

测试容器镜像的下载速度

CI 是自动化的关键组成部分,减少流水线执行中的每一秒都至关重要。下载时间也会影响新容器镜像推送到生产环境的速度。因此,我们需要考虑所使用容器镜像的下载速度。

在这里,我们将使用 Linux 中的 time 命令执行 docker run,并在指定的容器基础镜像中比较该过程中实际用户 CPU 时间和系统 CPU 时间的总结:

  1. debian:buster-slim Debian 基础镜像中安装 curl 工具:

    $ time docker run --rm debian:buster-slim sh -c "apt-get update && apt-get install curl -y"
    real    0m43.837s
    user    0m0.024s
    sys     0m0.043s
    
  2. 进行比较时,现在线我们在alpine:3.12镜像中运行相同的命令:

    $ time docker run --rm alpine:3.12 sh -c "apk update && apk add --update curl"
    real    0m2.644s
    user    0m0.034s
    sys     0m0.021s
    

请注意,这两个镜像在本地注册表中都不可用,首次从公共 Docker Hub 拉取。正如你所见,Alpine 镜像几乎在 2 秒内完成任务,而相同的请求使用 Debian 镜像时则多花了超过 40 秒的时间才完成。

现在我们已经了解了基于不同基础镜像测量容器中命令执行速度的情况。让我们将本节所学的所有内容总结为一个简短的容器镜像最佳实践清单。

应用容器基础镜像最佳实践

从技术上讲,大多数应用程序将运行在所有常见且流行的容器基础镜像之上的容器中。这可能适用于开发和测试目的,但在将任何容器镜像推向生产环境之前,我们应该考虑一些常识性的最佳实践:

  • 容器镜像的大小很重要,只要容器基础镜像不会引入性能损失和漏洞。相比节省几兆字节,使用稳定、兼容和支持的基础镜像是更优的选择。

  • 在构建容器镜像时,切勿使用latest标签来拉取基础镜像。

  • 确保使用经过测试和验证的容器镜像的确切版本。你也可以通过将<image-name>:<tag>替换为<image-name>@<digest>来指定其摘要,从而生成稳定且可重复的构建。

  • 检查应用程序清单中的imagePullPolicy。除非另有要求,建议使用IfNotPresent

  • 在可能的情况下,尽量在容器主机和容器镜像中使用相同的基础操作系统。

  • 将镜像漏洞扫描工具集成到 CI 流水线中,确保在将镜像部署到生产环境之前,清除至少高危和严重漏洞。

  • 监控容器镜像大小随时间的变化,并在发现突发的大幅度变化时通知维护人员。

  • 使用公共容器注册表时,建议将容器镜像存储在多个注册表中。一些公共注册表包括 Docker Hub、GitLab Container Registry、Red Hat Quay、Amazon ECR、Azure Container Registry 和 Google Cloud Container Registry。

  • 为了提高安全性,使用私有容器注册表,并监控公共容器注册表拉取到生产环境的情况。

现在我们已经了解了选择容器镜像和生产最佳实践的挑战。接下来,我们将看看不同的部署策略及其应用场景。

学习应用程序部署策略

没有应用程序部署策略设计专业知识的组织,在将其服务投入生产用户之前,可能会在管理应用生命周期时面临巨大的操作复杂性。许多用户在数字化转型过程中仍然面临容器和微服务采用问题,最终不得不回退到更昂贵的数据库即服务DbaaS)模型,甚至使用传统的虚拟机部署方法。为了避免常见的错误和生产中的反模式,我们需要意识到一些常见的策略,这些策略将确保我们在 Kubernetes 上成功部署和管理应用程序。

我们在第七章中的部署有状态应用程序部分学习了不同 Kubernetes 控制器之间的区别,如 Deployments、ReplicaSets 和 StatefulSets,管理存储和有状态应用程序

在本节中,我们将学习以下容器化应用部署最佳实践:

  • 选择部署模型

  • 监控部署

  • 使用就绪探针和存活探针

让我们在接下来的部分中讨论每个策略。

选择部署模型

在 Kubernetes 中,应用程序可以按照不同的部署流程进行发布。选择正确的策略并不总是容易的,因为它实际上取决于你的服务以及应用程序如何被用户访问。现在,我们将回顾最常见的模型:

  • A/B 测试

  • 蓝绿部署

  • 金丝雀发布

  • 清洁部署

  • 增量部署

让我们在接下来的部分中了解每种方法的优点。

A/B 测试

A/B 测试部署允许根据 HTTP 头、位置、浏览器 Cookie 或其他用户元数据等条件将一组用户路由到新的部署。A/B 测试部署适用于需要在特定用户群体中测试应用程序的某个功能,并且根据转化率继续推广的情况。价格和用户体验测试也使用 A/B 测试。除了需要管理的参数的复杂性外,这是最灵活的模型,具有低云成本、对用户的最小影响和快速回滚时间。

蓝绿部署

在蓝绿部署模型中,集群中每个应用程序的实例数量相等。该模型可以通过流量切换或当使用像 Istio 这样的服务网格时通过流量镜像来执行。它适用于在没有实际用户影响的情况下测试服务变更的负载和合规性。当指标返回成功数据时,新部署(绿色)将被推广。该模型不能用于针对特定用户群体,并且由于其完全部署模型,在云资源消耗方面可能会很昂贵。

金丝雀发布

金丝雀部署基于百分比逐渐将流量从一个部署切换到另一个部署,有时会根据成功率或健康状况等指标触发。当对新版本的信心不高或在完全新平台上部署版本时,金丝雀发布是首选。无法针对特定用户组进行部署。此方法不会增加公共云成本,并且回滚时间可能非常快。

清洁部署

在这种方法中,一个版本的应用程序被销毁,新的版本被部署。由于这是最简单的方法,因此在部署时首选此方法,尽管除非服务未使用,否则不应在生产环境中使用。如果部署失败,相比其他方法,回滚时间将是最高的,服务停机时间也将是最长的。

增量部署

在这种方法中,应用程序的新版本以滚动更新的方式部署并逐步迁移。与全新部署相比,这种模型唯一的优点是增量部署不会引入停机时间。

一些方法只能通过 服务网格 解决方案来实现,例如 Istio、Linkerd 或 AWS App Mesh,以及包括 Contour、Gloo、NGINX 或 Traefik 在内的入口控制器。

多种部署策略的编排可能变成一个复杂的配置难题。在这种情况下,应用程序交付操作符的使用会非常有帮助。Flagger 是 Kubernetes 生态系统中最完整的渐进交付 Kubernetes 操作符之一。Flagger 可以使用 Istio、Linkerd、App Mesh、NGINX、Skipper、Contour、Gloo 或 Traefik 基于 Prometheus 收集的指标分析自动化复杂的滚动场景。要了解更多有关 Flagger 操作符以及涵盖这里讨论的模型的教程,你可以阅读官方文档,网址是 docs.flagger.app/

监控部署

平稳、生产就绪的应用程序部署和金丝雀分析无法在没有监控应用程序使用指标的情况下实现。我们可以使用诸如 Prometheus、Datadog 或 Splunk 等工具来监控我们的应用程序。

我们将在第九章《监控、日志记录与可观测性》中介绍监控、可视化、日志记录、追踪解决方案,以及如何制作与生产需求相关的可视化仪表板。

使用就绪性和存活性容器探针

当新的 Pod 在我们的 Kubernetes 集群中被调度时,其阶段由 PodStatus 对象表示。这些阶段报告为 PendingRunningSucceededFailedUnknown,它们并不代表或保证我们的应用程序的预期功能。你可以在 Kubernetes 官方文档网站上阅读有关 Pod 生命周期及其各阶段的更多信息,网址是 kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/

为了监控容器内应用程序的真实健康状态,可以执行定期的诊断任务。这些周期性执行的诊断测试被称为 kubelet,可以执行三种类型的容器探针,具体如下:

  • livenessProbe

  • readinessProbe

  • startupProbe

强烈建议至少使用就绪探针和存活探针,在 Kubernetes 集群中调度应用程序时,控制应用程序的健康状况,并定期检查其状态。当启用时,kubelet 可以调用三种不同的处理程序,ExecActionTCPSocketActionHTTPGetAction,以验证应用程序的健康状态。

重要提示

您可以在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter08/probes/liveness/busybox.yaml 找到完整的源代码。

在接下来的代码片段中,我们将创建一个使用 livenessProbebusybox Pod 示例,以执行容器镜像中的命令来检查 Pod 的存活状态。

probes/liveness/busybox.yaml 路径下创建 busybox Pod 的模板:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-execaction
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/alive; sleep 30; rm -rf /tmp/alive; sleep 300
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/alive
      initialDelaySeconds: 10
      periodSeconds: 10

当容器启动时,它会执行 args 部分指定的命令。该命令首先在 /tmp/alive 下创建一个文件,然后等待 30 秒并将其删除。livenessProbe 如同在同一文件中指定的那样,首先等待 10 秒(由 initialDelaySeconds 参数定义),然后每 10 秒(由 periodSeconds 参数定义)定期执行 cat /tmp/alive 命令。在前 30 秒,命令将成功执行;一旦文件被删除,livenessProbe 将失败,Pod 将因丧失存活状态而被重启。确保通过设置合理的 initialDelaySeconds 值来为 Pod 启动留出足够的时间。

同样,我们可以通过将 livenessProbe 字段替换为 readinessProbe 来添加 readinessProbe

现在我们已经学习了 Kubernetes 上生产部署的最佳实践,也了解了滚动生产应用程序的常见部署策略,以及如何使用容器探针验证应用程序的健康状况。接下来,我们将学习如何扩展应用程序。

扩展应用程序并实现更高的可用性

Kubernetes 容器编排平台提供了广泛的功能,帮助我们以可扩展和高可用的方式部署应用程序。在设计支持水平扩展服务和应用程序的架构时,我们需要了解一些常见的策略,这些策略有助于我们在 Kubernetes 集群上成功地扩展应用程序。

在前一节中,学习应用程序部署策略,我们介绍了一些有助于扩展应用程序的策略,包括部署策略和通过容器探针实现健康检查。在本节中,我们将学习如何使用水平 Pod 自动缩放器HPA)来扩展应用程序。

当我们首次在 Kubernetes 集群上部署应用程序时,应用程序很可能不会立即被访问,使用量会随着时间的推移逐渐增加。在这种情况下,推出多个副本的部署会浪费我们的基础设施资源。Kubernetes 中的 HPA(水平 Pod 自动缩放)帮助我们在不同场景下增加必要的资源。

重要说明

你可以在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter08/hpa/deployment-nginx.yaml 找到完整的源代码。

现在,我们将学习如何基于 CPU 利用率指标配置基础的 HPA。你可以在 Kubernetes 官方文档网站上阅读更多关于 HPA 的内容,网址为 kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/

  1. 如果你之前没有安装过,确保通过执行以下命令安装 Metrics Server

    $ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.4.1/components.yaml
    
  2. hpa/deployment-nginx.yaml 路径下创建一个名为 nginx-hpa 的部署,并设置 replicas 数量为 1。确保设置 resources.request.cpu,否则 HPA 将无法正常工作。在我们的示例中,我们使用了 NGINX 部署。你可以使用任何你想要应用 HPA 的部署:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-hpa
      namespace: default
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nginx-hpa
      template:
        metadata:
          labels:
            app: nginx-hpa
        spec:
          containers:
          - name: nginx-hpa
            image: nginx:1.19.6
            ports:
            - containerPort: 80
            resources:
              requests:
                cpu: "200m"
    
  3. 执行以下命令以创建部署:

    $ kubectl apply -f deployment-nginx.yaml
    
  4. 通过检查状态确认部署是否成功:

    $ kubectl get deployments
    NAME        READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-hpa   1/1     1            1           11s
    
  5. 现在在 hpa/hpa-nginx.yaml 路径下创建一个名为 nginx-autoscale 的 HPA,设置 minReplicas 数量为 1maxReplicas 数量为 5,并将 targetCPUUtilizationPercentage 设置为 50

    apiVersion: autoscaling/v1
    kind: HorizontalPodAutoscaler
    metadata:
    name: nginx-autoscale 
      namespace: default
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: nginx-hpa
      minReplicas: 1
      maxReplicas: 5
      targetCPUUtilizationPercentage: 50
    
  6. 执行以下命令以创建部署:

    $ kubectl apply -f hpa-nginx.yaml
    
  7. 确认我们的 HPA 是否成功创建:

    $ kubectl get hpa
    NAME              REFERENCE              TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
    nginx-autoscale   Deployment/nginx-hpa   0%/50%   1         5         0          15s
    
  8. 上述命令的输出应如下所示:

图 8.5 – 使用 HPA 监控 CPU 指标以扩展应用程序

图 8.5 – 使用 HPA 监控 CPU 指标以扩展应用程序

在前面的示例中,我们使用了 CPU 利用率作为指标。HPA 可以使用多个指标,包括 CPU、内存以及其他自定义的外部指标,如服务延迟和 I/O 负载,通过自定义指标适配器。除了 HPA,我们还可以使用Pod 中断预算PDB)来避免自愿和非自愿的中断,从而提供更高的可用性。你可以在 kubernetes.io/docs/tasks/run-application/configure-pdb/ 上阅读更多关于为应用程序指定 PDB 的内容。

总结

在本章中,我们探讨了容器镜像的组成部分,创建容器镜像的最佳实践以及选择正确的基础镜像类型。我们通过删除不必要的文件和使用多阶段构建来减小容器镜像的大小。我们学习了如何主动扫描容器镜像中的漏洞。我们学习了关于应用部署策略,测试和推出应用程序新功能和版本的方法。我们创建了一个 HPA 来扩展我们的应用程序。本章提到的所有建议和最佳实践都帮助我们减少攻击面,增加稳定性,以提高生产环境中的效率。

在下一章中,我们将学习 Kubernetes 的可观察性以及在生产环境中监视的关键指标。我们将了解要使用或构建的工具和堆栈,比较生态系统中的最佳工具,并学习如何从站点可靠性的角度处理可观察性。

进一步阅读

您可以参考以下链接了解本章涵盖的主题的更多信息:

第九章:第九章:监控、日志记录与可观察性

在前几章中,我们学习了如何在 Kubernetes 上部署应用的最佳实践,以实现架构现代化。我们了解了 Kubernetes 如何在一组容器主机上创建一个抽象层,使应用部署更加简便,同时也改变了开发团队相较于传统单体应用的责任。采用微服务架构需要实施新的可观察性实践,以高效监控 Kubernetes 平台引入的各层结构。无论你计划扩展现有的监控堆栈以包括 Kubernetes,还是寻找完整的云原生解决方案,了解关键的监控指标并制定提升可观察性的策略,在故障排除和采取有效措施时至关重要。

在本章中,我们将讨论关键的基础设施组件和 Kubernetes 对象指标。我们将了解如何定义生产环境中的服务级目标SLOs)。我们将学习市场上可用的监控和日志记录堆栈与解决方案,以及何时使用它们。我们将学习如何部署核心的可观察性(监控和日志记录)堆栈,为基础设施使用仪表板,并通过添加新仪表板来微调应用程序的可观察性,结合可视化工具使用。通过本章的学习,你将能够检测集群和应用程序的异常,并准确找出关键问题。

在本章中,我们将覆盖以下主要内容:

  • 理解 Kubernetes 可观察性面临的挑战

  • 学习网站可靠性最佳实践

  • 监控、指标和可视化

  • 日志记录与追踪

技术要求

你应该已经安装了前几章中提到的以下工具:

  • kubectl

  • Helm 3

  • metrics-server

  • KUDO Operator

  • cert-manager

  • 一个 Cassandra 实例

你需要根据 第三章《使用 AWS 和 Terraform 配置 Kubernetes 集群》的说明,准备好并运行 Kubernetes 集群。

本章的代码位于github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter09

请查看以下链接,观看《代码实战》视频:

bit.ly/36IMIRH

理解 Kubernetes 可观察性面临的挑战

在本节中,我们将从 Kubernetes 角度了解监控与可观察性之间的区别。我们将保留需要监控的关键指标,以便快速解决故障。在讨论最佳实践并选择监控选项之前,让我们了解在 Kubernetes 中被认为重要的指标是什么。

探索 Kubernetes 指标

当我们在 第八章 《无缝且可靠应用的部署》中探索容器镜像的组件时,我们还比较了单体架构和微服务架构,并了解了 容器主机 的功能。当我们将应用容器化时,我们的容器主机 (2) 需要在操作系统之上运行容器运行时 (4) 和 Kubernetes 层 (5),以协调 Pod 的调度。然后,我们的容器镜像 (6) 会在 Kubernetes 节点上进行调度。在调度操作期间,运行在这些新层上的应用程序状态需要被探测(见 图 9.1):

图 9.1 – 单体架构与微服务架构监控层的比较

图 9.1 – 单体架构与微服务架构监控层的比较

考虑到我们引入的所有新层次和故障点,我们可以将最重要的指标总结为三大类:

  • Kubernetes 集群健康和资源利用率指标

  • 应用部署和 Pod 资源利用率指标

  • 应用健康和性能指标

在生产集群中,由于资源不足或缺少标签和注释,遇到调度问题是非常常见的。当发生调度问题时,您的应用程序可能会迅速进入不稳定状态,直接影响服务的可用性。多个原因可能触发这些问题,开始故障排除的最佳方法是观察关键集群健康和资源利用率指标的变化。Kubernetes 提供了每个层级的详细信息,以检测影响集群性能的瓶颈。

大多数有用的指标可以通过 Metrics API 和 HTTP 服务器的 /metrics 端点实时获取。建议定期将指标抓取到类似 Prometheus 服务器的时间序列数据库中进行存储。您可以在 Kubernetes 官方文档网站上阅读更多关于资源指标管道的内容:kubernetes.io/docs/tasks/debug-application-cluster/resource-metrics-pipeline/

下面是我们需要关注的一些有用的集群资源和内部指标的简要列表。

Kubernetes 集群健康和资源利用率指标

活跃节点的数量是一个关键指标,它可以告诉我们集群成本和健康的直接影响。可以通过观察此处列出的指标来查看节点资源的利用率:

  • CPU 利用率、CPU 请求承诺和 CPU 限制承诺

  • 内存使用情况、内存请求承诺和内存限制承诺

  • 网络 I/O 压力

  • 磁盘 I/O、磁盘空间使用情况和卷空间使用情况

Kubernetes 控制平面在多个组件的帮助下做出关键的调度决策,包括 Kubernetes API 服务器(kube-apiserver)、高可用键值存储(etcd)、调度器功能(kube-scheduler)以及处理 Kubernetes 控制循环的守护进程(kube-controller-manager)。Kubernetes 控制平面通常运行在专用的主节点上。因此,控制平面的健康和可用性对我们集群的调度能力至关重要。我们可以通过观察此处列出的指标来监控控制平面的状态:

  • API 服务器的可用性和 API 服务器读/写服务级指标(SLI)

  • etcd 的正常运行时间和 etcd 总领导者选举次数

  • 调度器的正常运行时间、调度速率、POST 请求延迟和 GET 请求延迟

  • 控制器管理器的正常运行时间、工作队列添加速率和工作队列延迟

此处列出的所有指标共同指示了我们 Kubernetes 集群中的资源和控制平面可用性。

应用部署和 Pod 资源利用率指标

从应用 Pod 和部署健康监控的角度来看,资源分配是我们需要关注的重点。我们可以观察以下按 Kubernetes 构造(如 Pod、部署、命名空间、工作负载和 StatefulSets)分类的指标,以便排查待处理或失败的部署:

  • 计算资源(按命名空间、Pod 和工作负载)

  • StatefulSet 期望副本数与当前版本的副本数

  • Kubelet 的正常运行时间、Pod 启动时长和操作错误率

我们应当关注单个节点资源利用率的异常,以保持在节点之间均匀分布 Pod。我们还可以通过命名空间或工作负载的资源利用率来计算项目和团队的费用分摊。

应用健康和性能指标

Pod 和部署的资源利用率,甚至它们的状态,通常不能为我们提供应用的完整视图。每个应用都有不同的期望,因此也会有特定的应用提供的指标需要关注。例如,对于 Prometheus 应用,诸如目标同步、抓取失败、附加的样本以及正常运行时间等指标是我们需要关注的。而对于其他应用,例如 Cassandra,我们可能需要关注如节点总数、故障节点数、修复比率、集群操作、读写操作、延迟、超时等指标。在本章稍后的 使用 Grafana 监控应用 部分,我们将学习如何为我们的应用启用指标导出器,并将其仪表板添加到 Grafana 中进行监控。

现在,我们已经了解了一些 Kubernetes 观察性挑战和关键指标。接下来,让我们看看如何运用我们的知识,通过最佳网站可靠性实践来应用于实际生产用例。

学习网站可靠性最佳实践

在本节中,我们将了解行业站点可靠性专家在面对技术性站点可用性问题时的考虑因素和最佳实践。

站点可靠性工程SRE)是 Google 工程团队提出的一门学科。Google 的核心服务大规模运营的方式至今仍然代表着 SRE 最佳实践的典范。你可以在 Google SRE 资源网站上了解更多关于其基础和实践的内容,网址是 sre.google/resources/。在我们学习监控和指标可视化工具之前,先来看看一些常识性的 SRE 最佳实践:

  • 尽可能自动化,并立即自动化:SRE 应该抓住一切机会来自动化耗时的基础设施任务。作为 DevOps 文化的一部分,SRE 与自主团队合作,选择自己的服务,这使得工具的统一几乎变得不可能,但任何针对工具和服务的标准化努力都能帮助小型 SRE 团队支持非常大的团队和服务。

  • 使用增量部署策略:在 第八章,《部署无缝且可靠的应用程序》一节中,我们学习了不同服务的替代部署策略,这些策略可以帮助你实施这一最佳实践。

  • 定义有意义的警报,并设定正确的响应优先级和行动:如果我们所有的通知和警报都发送到一个桶或者邮箱地址,无法期待 SRE 对不同级别的响应速度。应将警报分类,至少分为三类或更多响应类别,如 必须立即响应(呼叫器),稍后响应(工单),和 可供分析的日志(日志)。

  • 为扩展做好规划,并始终预期故障:设定资源利用阈值,并规划容量以应对服务过载和基础设施故障。混沌工程也是一种非常好的实践,能够帮助避免生产环境中的意外情况。

  • 从最终用户的角度定义 SLO:这包括优先考虑客户端度量,而非服务器端度量。如果用户体验的延迟较高,那么单靠服务器端的正向指标是不能接受的。

现在我们已经了解了 Kubernetes 可观察性挑战和站点可靠性最佳实践。接下来,我们将探讨如何在 Kubernetes 上部署监控栈,并可视化从度量导出工具收集到的指标。

监控、指标和可视化

在本节中,我们将了解云原生生态系统中流行的监控解决方案,以及如何快速启动和运行监控栈。监控、日志记录和追踪常常被误用为可互换的工具,因此,理解每个工具的目的非常重要。

最新的 2020 云原生计算基金会CNCF)调查表明,企业使用多个工具(平均五个或更多)来监控它们的云原生服务。流行工具和项目的名单包括 Prometheus、OpenMetrics、Datadog、Grafana、Splunk、Sentry、CloudWatch、Lightstep、StatsD、Jaeger、Thanos、OpenTelemetry 和 Kiali。研究表明,最常见和最被采纳的工具是开源的。你可以通过访问 radar.cncf.io/2020-09-observability 阅读更多关于 CNCF 社区雷达观察的内容。

Prometheus 和 Grafana 一起使用是 Kubernetes 工作负载的最相关组合解决方案。本书无法涵盖所有工具,因此我们将重点介绍流行的 Prometheus 和 Grafana 解决方案。我们将学习如何安装这些技术栈,以获取一些核心集群和应用程序的度量数据。

在 Kubernetes 上安装 Prometheus 技术栈

Prometheus 是生态系统中最被采纳的开源监控和告警解决方案。Prometheus 提供了一个多维数据模型,并使用一个灵活的查询语言,名为 kube-state-metrics,以及 Grafana。你可以通过访问官方 Prometheus 文档站点 prometheus.io/docs/introduction/overview/ 阅读更多关于 Prometheus 及其概念的信息。

现在,让我们使用 kube-prometheus-stack(前身为 Prometheus Operator)安装 Prometheus,并准备我们的集群开始监控 Kubernetes API 服务器的变化:

  1. 创建一个名为 monitoring 的命名空间:

    $ kubectl create ns monitoring
    
  2. kube-prometheus-stack Helm Chart 仓库添加到你的本地仓库列表:

    $ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
    
  3. 将 Helm stable chart 仓库添加到你的本地仓库列表:

    $ helm repo add stable https://charts.helm.sh/stable
    
  4. 更新 Helm Chart 仓库:

    $ helm repo update
    
  5. 从其 Helm 仓库安装 kube-prometheus-stack

    $ helm install --namespace monitoring prometheus prometheus-community/kube-prometheus-stack
    
  6. 执行以下命令验证安装是否成功:

    $ kubectl get pods -n monitoring
    
  7. 上述命令的输出应如下所示:图 9.2 – 安装成功后运行的 Prometheus Pod 列表

    图 9.2 – 安装成功后运行的 Prometheus Pod 列表

  8. 现在我们已经安装了 kube-prometheus-stack。让我们访问包含的 Grafana 服务实例。创建端口转发以便在本地访问 Prometheus 界面和 Grafana 仪表板:

    NodePort to LoadBalancer.
    
  9. 执行以下命令验证服务 IP:

    $ kubectl get svc -n monitoring
    
  10. 上述命令的输出应如下所示:图 9.3 – 监控命名空间中暴露的服务列表

    图 9.3 – 监控命名空间中暴露的服务列表

  11. 如果您使用了端口转发,您可以通过 http://localhost:9090(用于 Prometheus)和 http://localhost:3000(用于 Grafana)访问主机上的服务界面。如果您改用 LoadBalancer,则可以使用 kubectl get svc -nmonitoring 命令输出的外部 IP 和端口地址。您将看到一个类似以下的 Grafana 登录界面:图 9.4 – Grafana 服务登录界面

    图 9.4 – Grafana 服务登录界面

  12. 使用默认的 admin Grafana 用户名和 prom-operator 密码访问 Grafana 仪表盘。如果您使用了自定义密码,可以通过执行以下命令从其 secret 资源中获取密码:

    $ kubectl get secret \
         --namespace monitoring prometheus-grafana \
         -o jsonpath="{.data.admin-password}" \
         | base64 --decode ; echo
    
  13. 点击仪表盘左上角的 搜索 按钮,搜索可用的仪表盘并选择您想查看的仪表盘。您可以通过选择 Kubernetes / 计算资源 / 集群 仪表盘,查看命名空间中 Pod 使用的集群资源消耗,类似以下截图所示:

图 9.5 – Grafana 中的 Kubernetes 集群资源仪表盘

图 9.5 – Grafana 中的 Kubernetes 集群资源仪表盘

作为 kube-prometheus 堆栈的一部分,您可以立即开始监控约 20 个仪表盘。以下是一些重要仪表盘的列表:

  • etcd

  • Kubernetes: API 服务器

  • Kubernetes / 计算资源 / 集群 - 命名空间(pods)、命名空间(工作负载)、节点(pods)、Pod、工作负载

  • Kubernetes / 控制器管理器

  • Kubernetes / Kubelet

  • Kubernetes / 网络 / 集群 - 命名空间(Pods)、命名空间(工作负载)、Pod、工作负载

  • Kubernetes / 持久化存储卷:

  • Kubernetes / 代理

  • Kubernetes / 调度器

  • Kubernetes / StatefulSets

  • 节点

我们现在已经学习了如何获取必要的组件,以便在 Kubernetes 集群上运行基于 Prometheus 的监控堆栈。接下来,让我们向 Grafana 实例添加新的仪表盘,以便监控我们的应用。

使用 Grafana 监控应用程序

Grafana 是一个开源的可观察性平台,用于通过插件可视化来自各种数据库的数据。Grafana 常常与 Prometheus 配合使用,以可视化来自 Kubernetes 端点的度量指标。Grafana 拥有庞大的社区,使得创建可观察性仪表盘或使用其官方和社区驱动的仪表盘变得非常容易。现在,我们将学习如何将额外的仪表盘添加到 Grafana 界面,以观察我们的应用状态。

您可以在官方 Grafana 文档网站上阅读更多关于 Grafana 及其概念的内容:grafana.com/docs/grafana/latest/

第七章,《管理存储和有状态应用程序》一节的 有状态工作负载操作符 部分中,我们使用 KUDO 部署了一个 Cassandra 实例。在这里,我们将使用现有的实例并将一个仪表盘添加到 Grafana,以监控其状态。如果你没有部署 Cassandra 实例,可以参考 第七章,《管理存储和有状态应用程序》中的说明来配置它,或者使用这些说明作为监控其他应用程序的指南。

现在,启用我们现有 Cassandra 实例上的 Prometheus 导出器,并添加仪表盘:

  1. 默认情况下,我们的 KUDO 操作的应用实例上的 Prometheus 导出器是禁用的。我们可以通过执行以下命令来启用度量导出器:

    $ kubectl kudo update \
         -p PROMETHEUS_EXPORTER_ENABLED=true \
         --instance $instance_name -n $namespace_name
    
  2. 更新 servicemonitor 标签,以从我们的 Prometheus 实例中获取度量数据:

    $ kubectl label servicemonitor cassandra-monitor \
          -n $namespace_name release=prometheus --overwrite
    
  3. 点击 Grafana 界面左上角的 + 按钮,并选择 导入图 9.6 – 导入菜单视图以添加新的 Grafana 仪表盘

    图 9.6 – 导入菜单视图以添加新的 Grafana 仪表盘

  4. grafana.com/api/dashboards/10849/revisions/1/download 链接粘贴到 通过 garafana.com 导入 字段,并点击 加载 按钮。

  5. 在下一个屏幕中,选择 Prometheus 作为数据源,并点击 导入 按钮来加载仪表盘,类似于下面截图中显示的界面:

图 9.7 – 从 Grafana.com 导入新的仪表盘

图 9.7 – 从 Grafana.com 导入新的仪表盘

现在,我们已经学会了如何添加自定义仪表盘来监控 Kubernetes 中应用程序的状态。同样,您可以在 Grafana 网站上找到社区构建的仪表盘,grafana.com/grafana/dashboards,以便监控您的应用程序和常见的 Kubernetes 组件。

日志记录与追踪

本节我们将了解云原生生态系统中流行的日志解决方案,以及如何快速部署一个日志栈。

在 Kubernetes 上运行的应用程序的日志处理与传统应用程序的日志处理方式有很大不同。在单体应用中,当服务器或应用崩溃时,我们的服务器仍然可以保留日志。但在 Kubernetes 中,当一个 Pod 崩溃时,会调度一个新的 Pod,从而导致旧的 Pod 及其记录被清除。容器化应用与传统应用的主要区别在于我们如何以及在哪里运输和存储日志以备后用。

两个专注于云原生的流行日志栈是Elasticsearch、Fluentd 和 KibanaEFK)栈和Promtail、Loki 和 GrafanaPLG)栈。两者在设计和架构上有根本的差异。EFK 栈使用 Elasticsearch 作为对象存储,Fluentd 用于日志路由和聚合,Kibana 用于日志的可视化。PLG 栈基于由 Grafana 团队设计的一个水平可扩展的日志聚合系统,使用 Promtail 代理将日志发送到 Loki 集群。你可以在grafana.com/oss/loki/上了解更多关于 Loki 的信息。

在本节中,我们将专注于 EFK 栈作为我们的集中式日志解决方案。我们将学习如何安装该栈以存储和可视化我们的日志。

在 Kubernetes 上安装 EFK 栈

让我们按照以下步骤启动我们的日志解决方案。我们将从使用 Elasticsearch Operator 安装 Elasticsearch 开始,然后部署 Kibana 实例,最后添加 Fluent Bit 来聚合我们的日志:

重要提示

你可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter09/logging/eck/elastic.yaml找到完整的源代码。

  1. elastic Helm Chart 仓库添加到本地仓库列表中:

    $ helm repo add elastic https://helm.elastic.co
    
  2. 更新 Helm Chart 仓库:

    $ helm repo update
    
  3. 安装 eck-operator 及其自定义资源定义CRD)从其 Helm 仓库:

    $ helm install eck-operator \
         elastic/eck-operator --version 1.3.1
    
  4. 执行以下命令验证 CRD 已创建并且安装成功:

    kubectl -n elastic-system logs -f statefulset.apps/elastic-operator command.
    
  5. 上述命令的输出应如下所示:图 9.8 – 成功安装后运行的 ECK pod 列表和创建的 CRD

    图 9.8 – 成功安装后运行的 ECK pod 列表和创建的 CRD

  6. 创建一个名为logging的命名空间:

    $ kubectl create ns logging
    
  7. 创建一个名为 elastic 的 Elasticsearch 实例清单,设置所需的节点数量,并在 logging/eck/elastic.yaml 路径中将 NodeSets.count 设置为 3。如果你希望部署较新版本,请确保更改 version

    apiVersion: elasticsearch.k8s.elastic.co/v1
    kind: Elasticsearch
    metadata:
      name: elastic
      namespace: logging
    spec:
      version: 7.10.1
      nodeSets:
      - name: default
        count: 3
        config:
          node.store.allow_mmap: false
    
  8. 执行以下 kubectl 命令在集群中创建一个 Elasticsearch 实例:

    $ kubectl apply -f elastic.yaml
    
  9. 通过执行以下命令验证我们创建的 Elasticsearch 节点的状态:

    $ kubectl get pods -n logging 
    
  10. 上述命令的输出应如下所示:![图 9.9 – 所有 Elasticsearch 节点处于准备就绪状态]

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_09_009.jpg)

    图 9.9 – 所有 Elasticsearch 节点处于准备就绪状态

  11. 我们可以通过执行以下命令来验证 Elasticsearch pod 的状态:

    $ kubectl get elasticsearch -n logging 
    
  12. 上述命令的输出应如下所示:![图 9.10 – 所有 Elasticsearch pod 已准备好并正在运行]

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_09_010.jpg)

    图 9.10 – 所有 Elasticsearch pod 已准备好并正在运行

  13. 将为 elastic 用户创建的凭据存储在名为 ES_PASSWORD 的变量中:

    $ ES_PASSWORD=$(kubectl get secret \
         elastic-es-elastic-user -n logging \
         -o go-template='{{.data.elastic | base64decode}}')
    
  14. 获取在日志命名空间中创建的服务列表:

    $ kubectl get svc -n logging
    
  15. 前述命令的输出应如下所示:图 9.11 – Elasticsearch Operator 创建的服务列表

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_09_011.jpg)

    图 9.11 – Elasticsearch Operator 创建的服务列表

    重要提示

    当从我们的工作站访问时,我们可以通过以下命令创建端口转发,将服务端点转发到localhost$ kubectl port-forward service/elastic-es-http 9200

  16. 通过执行以下命令,使用我们保存的密码和服务名称获取 Elasticsearch 端点的地址:

    $ curl -u "elastic:$ES_PASSWORD" \
         -k https://elastic-es-http:9200
    
  17. 前述命令的输出应如下所示:图 9.12 – Elasticsearch Operator 创建的服务列表

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_09_012.jpg)

    图 9.12 – Elasticsearch Operator 创建的服务列表

    重要提示

    你可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter09/logging/eck/kibana.yaml找到完整的源代码。

  18. 现在,我们已经部署了 Elasticsearch 实例。接下来,部署一个 Kibana 实例,并将其与现有的 Elasticsearch 实例捆绑。创建一个名为kibana的 Kibana 实例清单,指定3个节点,并将其保存在logging/eck/kibana.yaml路径中。如果需要部署更新版本,请确保替换version字段:

    apiVersion: kibana.k8s.elastic.co/v1
    kind: Kibana
    metadata:
      name: kibana
      namespace: logging
    spec:
      version: 7.10.1
      count: 3
      elasticsearchRef:
        name: elastic
    
  19. 执行以下kubectl命令在集群中创建一个 Kibana 实例:

    $ kubectl apply -f kibana.yaml
    
  20. 通过执行以下命令来验证我们创建的 Kibana 节点的状态:

    $ kubectl get kibana -n logging 
    
  21. 前述命令的输出应如下所示:图 9.13 – 所有 Kibana 节点处于健康状态

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_09_013.jpg)

    图 9.13 – 所有 Kibana 节点处于健康状态

  22. 我们可以通过执行以下命令来验证相关 Kibana Pod 的状态:

    $ kubectl get pods -n logging  \
         --selector='kibana.k8s.elastic.co/name=kibana'
    
  23. 前述命令的输出应如下所示:图 9.14 – 所有 Kibana Pod 都已准备好并正在运行

    图 9.14 – 所有 Kibana Pod 都已准备好并正在运行

  24. 获取在日志命名空间中创建的服务列表:

    $ kubectl get svc -n logging \
         --selector='kibana.k8s.elastic.co/name=kibana'
    
  25. 当从本地工作站访问时,我们可以通过以下命令创建端口转发,将服务端点转发到localhost

    $ kubectl port-forward service/kibana-kb-http 5601
    
  26. 通过执行以下命令获取我们之前获得的elastic用户密码:

    $ echo $ES_PASSWORD
    
  27. 现在,在浏览器中打开https://localhost:5601。使用elastic用户和我们从上一步复制的密码访问 Kibana 界面。

    重要提示

    你可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/blob/master/Chapter09/logging/eck/fluent-bit-values.yaml找到完整的源代码。

  28. 现在,我们已经安装了 Elasticsearch 和 Kibana 实例。作为最后一步,让我们部署 fluent-bit 实例以聚合日志。创建一个名为 fluent-bit-values.yaml 的 Helm 配置文件。如果需要,请确保替换 host 地址和 http_password 参数:

    backend:
      type: es
      es:
        host: elastic-es-http
        port: 9200
        http_user: elastic
        http_passwd: ${ES_PASSWORD}
        tls: "on"
        tls_verify: "off"
    parsers:
      enabled: true
      regex:
        - name: log_parser
          regex: ^(?<logtimestamp>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<log>.*)$
          timeKey: logtimestamp
          timeFormat: "%Y-%m-%dT%H:%M:%S.%L%z"
    input:
      tail:
        parser: log_parser
    
  29. 将 Helm stable Chart 仓库添加到本地仓库列表:

    $ helm repo add stable https://charts.helm.sh/stable
    
  30. 更新 Helm Chart 仓库:

    $ helm repo update
    
  31. 从 Helm 仓库安装 fluent-bit

    $ helm install fluent-bit stable/fluent-bit \
         -n logging -f fluent-bit-values.yaml
    
  32. 通过执行以下命令验证安装是否成功:

    $ kubectl get pods -n logging
    
  33. 上述命令的输出应该如下所示:图 9.15 – 完成日志堆栈所需的所有 pods 列表

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_09_015.jpg)

    图 9.15 – 完成日志堆栈所需的所有 pods 列表

  34. 现在,我们将切换到浏览器中的 Kibana 界面。如果你关闭了浏览器窗口,请重复步骤 26步骤 27以访问 Kibana 界面。点击仪表板上的Kibana图标。

  35. 在 Kibana 入门仪表板上,点击添加数据按钮。仪表板应该看起来像下面的截图:图 9.16 – Kibana 的入门界面

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_09_016.jpg)

    图 9.16 – Kibana 的入门界面

  36. 现在,Kibana 会检测到 Fluent Bit 转发的数据。在下一个屏幕上,点击创建索引模式按钮,创建一个与我们的索引匹配的索引模式。

  37. 如下图所示,Fluent Bit 创建的索引遵循 kubernetes_cluster-YYY.MM.DD 模式。在这里,使用 kubernetes_cluster-* 作为我们的索引模式名称,然后点击下一步按钮继续:图 9.17 – 在 Kibana 上创建索引模式以匹配源数据

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_09_017.jpg)

    图 9.17 – 在 Kibana 上创建索引模式以匹配源数据

  38. 最后,在时间过滤器字段中输入 @timestamp,然后点击创建索引模式按钮以完成索引。

现在我们已经学习了如何在 Kubernetes 堆栈上部署基于 ECK 堆栈的日志解决方案,以聚合和可视化我们的集群日志。在生产环境中运行时,请确保将运行日志堆栈的集群与从中收集日志的集群分开。我们需要确保,在集群由于任何原因无法访问时,我们的日志以及用于故障排除的日志堆栈仍然可以访问。

总结

在本章中,我们探讨了 Kubernetes 的重要指标,并学习了 SRE(站点可靠性工程)在维持更高可用性方面的最佳实践。我们学习了如何搭建基于 Prometheus 和 Grafana 的监控与可视化堆栈,并将自定义应用仪表板添加到我们的 Grafana 实例中。我们还学习了如何在 Kubernetes 集群上搭建基于 Elasticsearch、Kibana 和 Fluent Bit 的 ECK 日志堆栈。

在接下来的最后一章中,我们将学习 Kubernetes 操作的最佳实践。我们将涵盖集群维护的主题,如升级与轮换、灾难恢复与避免、集群与应用故障排除、质量控制、持续改进和治理。

深入阅读

你可以参考以下链接,了解本章中涉及的主题的更多信息:

第十章:第十章: 高效操作与维护 Kubernetes 集群

在前几章中,我们学习了自动化 Kubernetes 及其基础设施组件的生产最佳实践。我们讨论了在集群中配置无状态工作负载的挑战,包括如何让持久化存储正常运行、选择容器镜像和部署策略。我们还了解了生态系统中重要的可观察性工具,并在集群中构建监控和日志堆栈,为故障排除需求提供坚实基础。一旦我们拥有一个生产就绪的集群并开始提供工作负载,维持集群的高效运作以保证集群的维护、可用性以及其他服务级目标SLOs)就变得至关重要。

本章将重点介绍 Kubernetes 操作最佳实践,并涵盖与集群维护相关的主题,如升级和轮换、备份、灾难恢复与避免、集群以及控制平面、工作节点和应用程序故障的故障排除。最后,我们将学习用于验证和改善集群质量的解决方案。

本章我们将讨论以下主要主题:

  • 学习集群维护和升级

  • 准备备份和灾难恢复

  • 验证集群质量

技术要求

你应该已经安装了以下工具(来自前几章):

  • AWS CLI v2

  • AWS IAM 认证器

  • kubectl

  • Terraform

  • Helm 3

  • metrics-server

  • MinIO 实例(作为备份的 S3 目标, 可选)

你需要根据 第三章 中的指示,拥有一个正常运行的 Kubernetes 集群,使用 AWS 和 Terraform 配置 Kubernetes 集群

本章的代码位于 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter10

查看以下链接,观看实际操作视频:

bit.ly/3aAdPzl

学习集群维护和升级

在本节中,我们将学习如何升级生产环境中的 Kubernetes 集群。通常,新的 Kubernetes 主版本会每季度发布一次,每个次版本在发布后的大约 12 个月内提供支持。按照软件升级的一般规则,除非是紧急的、时间敏感的安全补丁,否则不常在新版本发布后立即进行升级。云服务提供商也遵循相同的做法,并在向公众发布新镜像之前进行合规性测试。因此,云服务提供商的 Kubernetes 版本通常会滞后于 Kubernetes 上游发布的几个版本。如果你想了解最新的版本发布,可以在 Kubernetes 官方文档站点找到相关的发布说明,网址为 kubernetes.io/docs/setup/release/notes/

第三章使用 AWS 和 Terraform 部署 Kubernetes 集群 中,我们了解了集群部署和发布策略。我们还了解到,集群部署不是一次性任务,而是一个持续的过程,它影响集群的质量、稳定性和运营,甚至影响基于集群运行的产品和服务。在之前的章节中,我们建立了坚实的基础设施部署策略,现在我们将在本章中继续遵循它,实施生产级的升级最佳实践。

第三章使用 AWS 和 Terraform 部署 Kubernetes 集群 中,我们通过 Terraform 自动化了集群部署。现在让我们使用相同的集群,将其升级到一个更新的 Kubernetes 版本。

升级 kubectl

首先,我们将升级 kubectl 到最新版本。你的 kubectl 版本应该至少等于或大于你计划升级到的 Kubernetes 版本:

  1. 下载最新的 kubectl 二进制文件并将其复制到 bin 目录:

    $ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
    $ chmod +x ./kubectl && sudo mv ./kubectl /usr/local/bin/kubectl 
    
  2. 通过执行以下命令确认 kubectl 二进制文件已更新为较新版本:

    $ kubectl version --short Client Version: v1.20.1
    Server Version: v1.15.12-eks-31566f
    
  3. 现在,通过执行以下命令检查你的节点状态和版本:

    $ kubectl get nodes
    

    上述命令的输出应如下所示:

图 10.1 – kubectl 命令显示节点状态及其版本

图 10.1 – kubectl 命令显示节点状态及其版本

在这里,我们已经将 kubectl 升级到最新版本。接下来,继续进行下一步,升级我们的集群版本。

升级 Kubernetes 控制平面

AWS EKS 集群每次只能升级一个版本。这意味着,如果我们当前的版本是 1.15,我们可以先升级到 1.16,然后再升级到 1.17,以此类推。

重要说明

你可以在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter10/terraform 查找到完整的源代码。

让我们使用我们在第三章中也使用过的 Terraform 脚本来升级我们的控制节点,通过 AWS 和 Terraform 提供 Kubernetes 集群

  1. 编辑 terraform.tfvars 文件,该文件位于 Chapter10/terraform/packtclusters 目录下,并将 cluster_version 值增加到下一个发布版本号。在我们的示例中,我们已将版本从 1.15 升级到 1.16

    aws_region = "us-east-1"
    private_subnet_ids = [
      "subnet-0b3abc8b7d5c91487",
      "subnet-0e692b14dbcd957ac",
      "subnet-088c5e6489d27194e",
    ]
    public_subnet_ids = [
      "subnet-0c2d82443c6f5c122",
      "subnet-0b1233cf80533aeaa",
      "subnet-0b86e5663ed927962",
    ]
    vpc_id = "vpc-0565ee349f15a8ed1"
    clusters_name_prefix  = "packtclusters"
    cluster_version       = "1.16"        #Upgrade from 1.15
    workers_instance_type = "t3.medium"
    workers_number_min    = 2
    workers_number_max    = 3
    workers_storage_size  = 10
    
  2. 运行 terraform plan 命令以验证计划的更改,确保应用这些更改之前没有问题:

    terraform plan command completes successfully. There is one resource to change. We are only changing the cluster version:![Figure 10.2 – The terraform plan command output    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_10_002.jpg)Figure 10.2 – The terraform plan command output
    
  3. 执行 terraform apply 命令。当提示您确认本地更新时,输入 yes

    $ terraform apply
    

    在升级过程中,我们可以通过命令行或 AWS 控制台跟踪进度。AWS 控制台中的集群状态将类似于以下截图:

图 10.3 – AWS 控制台输出显示集群状态为“正在更新”

图 10.3 – AWS 控制台输出显示集群状态为“正在更新”

terraform apply 命令成功完成后,您将获得以下结果。此时,Terraform 已成功更改了一个 AWS 资源:

图 10.4 – terraform apply 命令输出

图 10.4 – terraform apply 命令输出

在这里,我们已经将 Kubernetes 控制平面更新到下一个可用版本。接下来,继续进行下一步,升级我们的节点组。

升级 Kubernetes 组件

升级 Kubernetes 控制平面并不会升级工作节点或我们的 Kubernetes 附加组件,如 kube-proxy、CoreDNS 和 Amazon VPC CNI 插件。因此,在升级控制平面后,如果需要,我们需要仔细升级每个组件到支持的版本。您可以在 Amazon EKS 文档网站上了解更多有关支持的组件版本和 Kubernetes 升级先决条件的内容:docs.aws.amazon.com/eks/latest/userguide/update-cluster.html。以下图示例显示了我们将在示例中遵循的升级路径支持矩阵表:

图 10.5 – Kubernetes 组件支持矩阵示例

图 10.5 – Kubernetes 组件支持矩阵示例

某些版本升级可能还需要在应用程序的 YAML 清单中进行更改,以引用新的 API。强烈建议使用持续集成工作流测试应用程序的行为。

现在我们的 EKS 控制平面已经升级,让我们升级 kube-proxy

  1. 执行以下命令以获取当前版本的 kube-proxy 组件:

    $ kubectl get daemonset kube-proxy --namespace kube-system -o=jsonpath='{$.spec.template.spec.containers[:1].image}'
    

    上述命令的输出应如下所示。请注意,您的账户 ID 和区域将不同:

    123412345678.dkr.ecr.us-east-1.amazonaws.com/eks/kube-proxy:v1.15.11-eksbuild.1
    
  2. 现在,通过使用之前命令的输出,将 kube-proxy 镜像升级到支持的版本,该版本见 图 10.5

    $ kubectl set image daemonset.apps/kube-proxy \
        -n kube-system \
        kube-proxy=123412345678.dkr.ecr.us-west-2.amazonaws.com/eks/kube-proxy:v1.16.15-eksbuild.1
    
  3. 运行步骤 1中的命令以确认版本更改。这时,前一个命令的输出应该如下所示:

    123412345678.dkr.ecr.us-east-1.amazonaws.com/eks/kube-proxy:v1.16.15-eksbuild.1
    
  4. 让我们了解如何在需要时升级 coredns。请注意,只有从 1.17 升级到 1.18 时,coredns 版本需要为 1.7.0。通过执行以下命令确认您的集群使用 coredns 作为 DNS 提供程序:

    $ kubectl get pod -n kube-system -l k8s-app=kube-dns
    

    前一个命令的输出应该如下所示:

    图 10.6 – 在 Kubernetes 集群中运行的 CoreDNS pod

    图 10.6 – 在 Kubernetes 集群中运行的 CoreDNS pod

  5. 通过执行以下命令获取当前 coredns 组件的版本:

    $ kubectl get deployment coredns --namespace kube-system -o=jsonpath='{$.spec.template.spec.containers[:1].image}'
    

    前一个命令的输出应该如下所示。请注意,您的账户 ID 和区域会有所不同:

    123412345678.dkr.ecr.us-east-1.amazonaws.com/eks/ coredns:v1.6.6-eksbuild.1
    
  6. 现在,使用前一个命令的输出,将 coredns 镜像升级到支持的版本(参考图 10.5):

    $ kubectl set image deployment.apps/coredns \
        -n kube-system \
        coredns=123412345678.dkr.ecr.us-west-2.amazonaws.com/eks/coredns:v1.7.0-eksbuild.1
    
  7. 运行步骤 1中的命令以确认版本更改。这时,前一个命令的输出应该如下所示:

    123412345678.dkr.ecr.us-east-1.amazonaws.com/eks/coredns:v1.7.0-eksbuild.1
    

在这里,我们已将 Kubernetes 组件更新到下一个可用版本。接下来,我们将继续升级工作节点。

升级 Kubernetes 工作节点

升级 AWS EKS 控制器后,我们将继续添加使用更新的 AMI 镜像的新工作节点。我们将排空旧节点,并帮助 Kubernetes 将工作负载迁移到新创建的节点。

让我们升级我们的工作节点:

  1. 编辑 Chapter03/terraform/packtclusters 目录下的 config.tf 文件,并将工作节点的 AMI ID 的版本从 1.15 更改为 1.16

    terraform {
      backend "s3" {
        bucket         = "packtclusters-terraform-state"
        key            = "packtclusters.tfstate"
        region         = "us-east-1"
        dynamodb_table = "packtclusters-terraform-state-lock-dynamodb"
      }
      required_version = "~> 0.12.24"
      required_providers {
        aws = "~> 2.54"
      }
    }
    provider "aws" {
      region  = var.aws_region
      version = "~> 2.54.0"
    }
    data "aws_ssm_parameter" "workers_ami_id" {
      name            = "/aws/service/eks/optimized-ami/1.16/amazon-linux-2/recommended/image_id"
      with_decryption = false
    }
    
  2. 编辑 Chapter03/terraform/packtclusters 目录下的 terraform.tfvars 文件,如果需要,增加 workers_number_min

    aws_region = "us-east-1"
    private_subnet_ids = [
      "subnet-0b3abc8b7d5c91487",
      "subnet-0e692b14dbcd957ac",
      "subnet-088c5e6489d27194e",
    ]
    public_subnet_ids = [
      "subnet-0c2d82443c6f5c122",
      "subnet-0b1233cf80533aeaa",
      "subnet-0b86e5663ed927962",
    ]
    vpc_id = "vpc-0565ee349f15a8ed1"
    clusters_name_prefix  = "packtclusters"
    cluster_version       = "1.16"
    workers_instance_type = "t3.medium"
    workers_number_min    = 2
    workers_number_max    = 5
    workers_storage_size  = 10
    
  3. 运行 terraform plan 命令来验证计划中的更改,确认无误后再应用:

    terraform plan command completes successfully. There is one resource to change. We are only changing the cluster version:![Figure 10.7 – The terraform plan command output    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_10_007.jpg)Figure 10.7 – The terraform plan command output
    
  4. 执行 terraform apply 命令。当提示确认进行就地更新时,输入 yes

    terraform apply command completes successfully. By then, Terraform has successfully changed one AWS resource:![Figure 10.8 – The Terraform command output after changes are applied](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_10_008.jpg)Figure 10.8 – The Terraform command output after changes are applied
    
  5. 执行 kubectl get nodes 命令以获取旧节点的名称。您将看到以下输出,正如我们所见,集群中三个节点中的两个仍在使用 v1.15.12:图 10.9 – 显示节点名称和版本的 kubectl 输出

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_10_009.jpg)

    图 10.9 – 显示节点名称和版本的 kubectl 输出

  6. 现在我们已经确认新节点已添加到集群中,接下来需要将 pod 从旧节点迁移到新节点。首先,一个一个地对旧节点进行标记,并将其排空:

    $ kubectl taint nodes ip-10-40-102-5.ec2.internal key=value:NoSchedule
    node/ip-10-40-102-5.ec2.internal tainted
    $ kubectl drain ip-10-40-102-5.ec2.internal --ignore-daemonsets --delete-emptydir-data
    
  7. 然后,从集群中移除旧节点。新的节点将自动创建并添加到集群中。让我们通过执行 kubectl get nodes 命令来确认所有节点已升级。该命令的输出应该如下所示:

图 10.10 – 显示更新后的节点版本的 kubectl 输出

图 10.10 – 显示更新后的节点版本的 kubectl 输出

我们现在已经学会了如何使用 Terraform 升级 Kubernetes 控制平面和工作节点。定期备份我们集群中的持久数据和应用程序是一种生产最佳实践。在接下来的部分,我们将专注于备份应用程序,并为我们的集群做好灾难恢复的准备。

为备份和灾难恢复做准备

在这一部分,我们将对运行在集群中的应用程序进行完整、即时或定期备份。并非每个应用程序都需要或能够利用常规备份。无状态应用程序的配置通常存储在 Git 仓库中,并且可以在需要时作为持续集成和持续交付CI/CD)流水线的一部分轻松部署。当然,对于有状态应用程序,如数据库、用户数据和内容,情况并非如此。我们的在线业务服务可能面临法律要求和行业特定法规的挑战,需要保留一定时间的数据副本。

由于集群外部或内部的原因,我们可能会丢失应用程序或整个集群,并且可能需要尽快恢复服务。在这种情况下,对于灾难恢复的使用场景,我们将学习如何使用存储在 S3 目标位置的备份数据来恢复服务。

在这一部分,我们将使用开源的 Velero 项目作为我们的备份解决方案。我们将学习如何安装 Velero,定期备份我们的数据并恢复它。

在 Kubernetes 上安装 Velero

传统备份解决方案以及云服务提供商提供的类似服务,主要关注保护节点资源。在 Kubernetes 中,运行在节点上的应用程序可以动态地在节点之间移动,因此备份节点资源无法满足容器编排平台的要求。云原生应用程序需要细粒度、了解应用程序的备份解决方案。这正是云原生备份解决方案(如 Velero)关注的方向。Velero 是一个开源项目,用于备份和恢复 Kubernetes 资源及其持久化卷。Velero 可以用于在 Kubernetes 资源上执行迁移操作和灾难恢复。你可以在 Velero 官方文档网站velero.io/docs/main/上阅读更多关于 Velero 及其概念的内容。

信息

你可以在github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter10/velero找到完整的源代码。

现在,让我们使用最新版本安装 Velero,并准备我们的集群开始备份我们的资源:

  1. 让我们获取velero的最新发布版本标签,并将其保存在名为VELEROVERSION的变量中:

    $ VELEROVERSION=$(curl –silent "https://api.github.com/repos/vmware-tanzu/velero/releases/latest" | grep '"tag_name":' | \
         sed -E 's/.*"v([^"]+)".*/\1/')
    
  2. 现在,下载最新的velero发布二进制文件,并通过执行以下命令进行安装:

    $ curl --silent --location "https://github.com/vmware-tanzu/velero/releases/download/v${VELEROVERSION}/velero-v${VELEROVERSION}-linux-amd64.tar.gz" | tar xz -C /tmp
    $ sudo mv /tmp/velero-v${VELEROVERSION}-linux-amd64/velero /usr/local/bin
    
  3. 确认 velero 命令可以执行:

    $ velero version
    Client:
            Version: v1.5.2
            Git commit: e115e5a191b1fdb5d379b62a35916115e77124a4
    <error getting server version: no matches for kind "ServerStatusRequest" in version "velero.io/v1">
    
  4. 在此 chapter10/velero/credentials-velero 路径中创建 Velero 用于访问您的 S3 目标的凭证文件。用您的 AWS ID 和访问密钥替换 aws_access_key_idaws_secret_access_key,然后保存该文件:

    [default]
    aws_access_key_id = MY_KEY
    aws_secret_access_key = MY_ACCESS_KEY
    
  5. 在运行以下命令之前,请使用您的 AWS S3 存储桶地址或兼容 S3 的对象存储(如 MinIO 对象存储服务器地址)更新 s3Url。通过执行以下命令安装 Velero 服务器组件:

    $ velero install \
        --provider aws \
        --plugins velero/velero-plugin-for-aws:v1.0.0 \
        --bucket velero \
        --secret-file ./credentials-velero \
        --use-restic \
        --backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://abcd123456789-1234567891.us-east-1.elb.amazonaws.com:9000
    

    前面命令的输出应如下所示:

    图 10.11 – Velero 安装程序输出显示安装成功

    ](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/k8s-prod-bst-prac/img/B16192_10_011.jpg)

    图 10.11 – Velero 安装程序输出显示安装成功

  6. 通过执行以下命令确认 Velero 服务器组件已成功安装:

    $ kubectl get deployments -l component=velero -n velero
    

    前面命令的输出应如下所示:

图 10.12 – Velero 部署状态显示已准备就绪

图 10.12 – Velero 部署状态显示已准备就绪

现在我们已经安装并配置了 Velero,以便将资源备份到 S3 目标。接下来,我们将学习如何对 Kubernetes 资源进行打包备份。

使用 Velero 备份特定资源

让我们按照此处的步骤获取我们想要保护的 Kubernetes 资源的备份。对于此示例,我们将需要一个有状态应用程序。我们将部署一个 MinIO 对象存储工作负载,上传一些文件,并对所有资源进行备份,以演示备份和恢复功能。您可以对任何想要的应用程序应用相同的步骤:

信息

您可以在 github.com/PacktPublishing/Kubernetes-in-Production-Best-Practices/tree/master/Chapter10/velero/backup 查找完整的源代码。

  1. 如果您已经有一个带有持久卷的有状态应用程序需要保护,您可以跳过到 步骤 4。否则,执行以下命令部署一个 MinIO 实例以继续此场景:

    $ kubectl apply -f https://raw.githubusercontent.com/PacktPublishing/Kubernetes-Infrastructure-Best-Practices/master/Chapter10/velero/backup/deployment-minio.yaml
    
  2. 通过执行以下命令验证 MinIO pod、服务和持久卷是否已创建:

    $ kubectl get pods,svc,pvc -nminio-demo
    

    前面命令的输出应如下所示:

    图 10.13 – MinIO pod、服务和持久卷的状态

    图 10.13 – MinIO pod、服务和持久卷的状态

  3. 现在,我们将为所有具有 app=minio 标签的资源创建备份。如果您使用不同的标签,请确保匹配选择器。执行以下命令以创建备份:

    --schedule="0 1 * * *" or --schedule="@daily" parameters. Later, you can get a list of scheduled backup jobs using the velero schedule get command.
    
  4. 运行以下命令以验证备份作业是否完成:

    $ velero backup describe minio-backup
    
  5. 作为替代方案,我们可以备份整个命名空间中的资源。让我们再做一次备份,这次使用命名空间,通过执行以下命令:

    $ velero backup create minio-backup-ns --include-namespaces minio-demo
    

现在,我们已经学习了如何在 Kubernetes 中创建第一个资源组和命名空间的备份。接下来,让我们模拟一个灾难场景,并测试恢复我们的应用程序。

使用 Velero 从备份恢复应用程序资源

让我们按照以下步骤完全删除命名空间中的资源,并恢复之前的备份以恢复它们。你可以对任何应用程序应用相同的步骤,以便从一个集群迁移到另一个集群。这种方法还可以作为集群升级策略,以减少升级时间:

  1. 通过执行以下命令删除应用程序命名空间中的所有资源:

    $ kubectl delete ns minio-demo
    
  2. 创建一个新的命名空间,并执行以下命令,从备份中恢复应用程序及其资源:

    $ kubectl create ns minio-demo
    $ velero restore create –from-backup minio-backup
    
  3. 等待几秒钟让资源恢复,并通过执行以下命令验证你的 MinIO pods、服务和持久卷是否已恢复:

    $ kubectl get pods,svc,pvc -nminio-demo
    
  4. 前面的命令输出应如下所示:

图 10.14 – MinIO pods、服务和持久卷的状态

图 10.14 – MinIO pods、服务和持久卷的状态

现在,我们已经学习了如何恢复生产 Kubernetes 集群中服务的资源组备份。接下来,让我们看看如何通过持续验证集群质量和排查问题来进行改进。

验证集群质量

在本节中,我们将学习生态系统中一些最佳实践和工具,以提高 Kubernetes 集群的各个方面的质量。持续改进是一个广泛的概念,涵盖了从提供流畅平台到在 Kubernetes 上提供服务、为资源设置特定的服务质量QoS),以及确保资源均匀分配和释放未使用资源,以减少集群资源的压力和提供服务的整体成本。改进的定义本身正逐渐变得更加细化,它并不限于我们将在这里讨论的实践。在了解一致性和成本管理工具之前,先让我们了解一些常识性的质量最佳实践,供我们参考:

  • 生成集群状态报告:尽管预计无论是由公有云提供商提供的托管 Kubernetes 服务、特定供应商提供的分发版本,还是基于上游 Kubernetes 的自管集群,Kubernetes 集群应表现一致,但实际上,可能会有一些限制和配置差异,我们应该进行验证。合规性测试是确保我们支持的集群被正确配置并符合官方 Kubernetes 规范的一个很好的方式。

  • GuaranteedBurstableBestEffort

  • 减少延迟,接近用户的位置:云服务提供商在不同地理位置提供集群是有原因的。通常,我们会从本地开始,观察最终用户的延迟和流量情况,然后再在不同的地理位置启动集群。观察问题和瓶颈,并在需要时扩展到更接近用户的其他区域。

  • 定义具有不同 QoS 的存储类:在 Kubernetes 集群中,CPU、内存以及一定程度上的网络 QoS 可以由 Kubernetes 管理。存储 QoS 预计由存储提供商处理。存储可以来自集群的外部存储或外部的超融合 容器附加存储 (CAS) 。最佳实践是将数据管理从特定存储供应商中抽象出来,以便使用存储类提供供应商无关的服务灵活性。根据应用的需求,可以使用不同的存储类来提供冷存储、SSD 或 NVMe 支持的存储。我们在第七章《管理存储和有状态应用程序》中了解了如何调整 Kubernetes 存储并选择存储解决方案。

  • 优化容器镜像:建议持续监控集群资源的主要消耗者,改善其消耗情况,并寻找优化消耗的方式。优化容器镜像对资源利用率和性能有显著影响。你可以在第八章《部署无缝且可靠的应用程序》中阅读有关改善容器镜像的挑战和最佳实践。

  • 优化集群资源支出:理论上,云服务提供商资源的唯一限制是你的预算。建议监控资源和项目分配的成本,以获取运行产品的全部成本。

现在我们已经学习了改善集群质量的最佳实践,并且已经涉及了前几章的一些内容。接下来,让我们深入了解尚未覆盖的其他领域,包括如何以非破坏性的方式验证集群资源,以及如何监控资源成本。

生成合规报告

有许多方法和工具可以让 Kubernetes 集群顺利运行。维护一个合适的配置是一个管理挑战。幸运的是,有一些工具可以验证报告并检测配置问题。Sonobuoy 是一个流行的开源工具,能够运行 Kubernetes 合规性测试并验证我们的集群健康状态。Sonobuoy 是集群无关的,可以生成我们集群特征的报告。这些报告用于确保最佳实践的应用,通过消除特定于发行版的问题,使得符合规范的集群能够迁移到我们的集群中。你可以在 Sonobuoy 的官方网站文档中了解更多关于使用插件和集成 端到端e2e)测试的自定义数据收集功能,网址:sonobuoy.io/docs/v0.20.0/。现在,让我们安装 Sonobuoy 的最新版本并通过运行 Kubernetes 合规性测试来验证集群:

  1. 获取 Sonobuoy 的最新发布版本标签,并将其保存在一个名为SONOBUOYVERSION的变量中:

    $ SONOBUOYVERSION=$(curl –silent "https://api.github.com/repos/vmware-tanzu/sonobuoy/releases/latest" | grep '"tag_name":' | \
         sed -E 's/.*"v([^"]+)".*/\1/')
    
  2. 现在,下载最新的 sonobuoy 发布二进制文件,并通过执行以下命令进行安装(github.com/vmware-tanzu/sonobuoy/releases/download/v0.20.0/sonobuoy_0.20.0_linux_amd64.tar.gz):

    $ curl --silent --location "https://github.com/vmware-tanzu/sonobuoy/releases/download/v${SONOBUOYVERSION}/sonobuoy_${SONOBUOYVERSION}_linux_amd64.tar.gz" | tar xz -C /tmp
    $ sudo mv /tmp/sonobuoy /usr/local/bin
    
  3. 确认 Sonobuoy 已安装,并且可以执行命令:

    $ sonobuoy version
    Sonobuoy Version: v0.20.0
    MinimumKubeVersion: 1.17.0
    MaximumKubeVersion: 1.99.99
    GitSHA: f6e19140201d6bf2f1274bf6567087bc25154210
    
  4. 确保集群有足够的资源来执行所有测试。你可以在 Sonobuoy 的源代码库中找到每个提供商的具体建议,网址:github.com/cncf/k8s-conformance/tree/master/v1.16。对于 EKS,建议的集群规模是 10 个 c5.xlarge 工作节点实例。在 EKS 集群上执行以下命令以启动合规性测试:

    $ sonobuoy run --wait \
         --sonobuoy-image projects.registry.vmware.com/sonobuoy/sonobuoy:v0.20.0
    
  5. 为了缩短测试时间并验证配置,而不是进行完整的认证合规性测试,我们可以使用 --mode quick 选项运行测试:

    $ sonobuoy run --wait --mode quick
    
  6. 验证过程可能需要最多一小时的时间,具体取决于执行的测试。完成后,执行以下命令获取插件的结果并检查失败的结果。有关检查结果的详细选项列表,请参阅文档:sonobuoy.io/docs/v0.20.0/results/

    $ results=$(sonobuoy retrieve) $ sonobuoy results $results
    

    上述命令的输出应如下所示:

    图 10.15 – Sonobuoy 验证结果

    图 10.15 – Sonobuoy 验证结果

  7. 删除集群中的 Sonobuoy 组件并清理资源:

    $ sonobuoy delete --wait
    

现在我们已经学习了如何验证 Kubernetes 集群配置。接下来,让我们看看如何检测过度配置的空闲资源,并优化集群的总成本。

管理和优化集群资源成本

监控项目成本和团队费用分摊,以及管理集群总支出,是在公共云提供商上管理 Kubernetes 的一些重大挑战。由于我们可以通过云供应商获得理论上无限的扩展能力,如果不加以管理,利用费用很容易迅速上涨并成为问题。Kubecost 帮助您监控并持续改进 Kubernetes 集群的成本。您可以在 Kubecost 的官方文档网站上了解更多关于成本和容量管理的功能:docs.kubecost.com/

现在,让我们使用 Helm 安装 Kubecost,并开始分析集群中的成本分配:

  1. 创建一个名为 kubecost 的命名空间:

    $ kubectl create ns kubecost
    
  2. cost-analyzer Helm Chart 仓库添加到本地仓库列表:

    $ helm repo add kubecost \
         https://kubecost.github.io/cost-analyzer/
    
  3. 更新 Helm Chart 仓库:

    $ helm repo update
    
  4. 从其 Helm 仓库安装 cost-analyzer

    $ helm install kubecost kubecost/cost-analyzer \     --namespace kubecost \     --set kubecostToken="bXVyYXRAbWF5YWRhdGEuaW8=xm343yadf98"
    
  5. 执行以下命令以验证安装是否成功:

    kube-state-metrics in the kubecost namespace. Your existing Prometheus and Grafana instance deployment of the node-exporter pod can get stuck in the pending state due to a port number conflict with the existing instances. You can resolve this issue by changing port number instances deployed with the Kubecost chart.
    
  6. 现在我们已经安装了 cost-analyzer。让我们访问 Kubecost 仪表盘。创建端口转发以便在本地访问 Kubecost 界面:

    NodePort to LoadBalancer.
    
  7. 打开浏览器窗口并访问 http://localhost:9090,该地址会转发到运行在集群中的 kubecost-cost-analyzer 服务。仪表盘将立即显示集群的月度运行成本,类似于以下内容:图 10.17 – Kubecost 可用集群屏幕

    图 10.17 – Kubecost 可用集群屏幕

  8. 从列表中点击您的集群,进入 Kubecost 仪表盘。仪表盘的顶部将显示总成本摘要和任何可能识别出的节省,类似于以下截图:图 10.18 – Kubecost 仪表盘

    图 10.18 – Kubecost 仪表盘

  9. 让我们向下滚动仪表盘屏幕,找到控制器组件和服务分配的摘要。在仪表盘的底部,我们将看到健康评分。健康评分是对基础设施可靠性和性能风险的评估:图 10.19 – Kubecost 仪表盘显示集群健康评估评分

    图 10.19 – Kubecost 仪表盘显示集群健康评估评分

  10. 仪表盘上最重要的快速摘要页面是健康评估和估算节省详情页面。让我们点击每个页面,查看您可以改进集群成本和性能的区域。在以下截图中,我们可以看到 Kubecost 在分析集群后提供的一个显著节省建议:图 10.20 – Kubecost 估算节省仪表盘

    图 10.20 – Kubecost 估算节省仪表盘

  11. 点击节省类别旁边的箭头按钮,查看优化集群成本的建议。

现在我们已经学会了如何通过优化请求大小、清理废弃工作负载以及采用多种方法管理集群中的未充分利用节点,来识别每月集群成本、资源效率、成本分配和潜在节省。

总结

本章中,我们探讨了 Kubernetes 操作的最佳实践,并涵盖了集群维护主题,如升级、备份和灾难恢复。我们学习了如何验证我们的集群配置,以避免集群和应用程序问题。最后,我们了解了如何检测和改善资源分配以及集群资源的成本。

通过完成本书的最后一章,我们现在具备了建立和管理符合生产级别的 Kubernetes 基础设施的完整知识,这些基础设施遵循行业最佳实践,并借鉴了早期技术采用者和现实中大规模 Kubernetes 部署的成熟技术。Kubernetes 提供了一个非常活跃的用户和合作伙伴生态系统。在本书中,我们重点介绍了目前已知的最佳实践。尽管原则不会迅速改变,但与每一项新技术一样,始终会有新的解决方案和新方法来解决相同的问题。如果您有任何建议,请通过前言部分提到的方式联系我们,帮助我们改进本书。

进一步阅读

你可以参考以下链接,获取更多关于本章所涉及主题的信息:

posted @ 2025-06-30 19:28  绝不原创的飞龙  阅读(5)  评论(0)    收藏  举报