在上一章中,你学习了如何手动在 Kubernetes 上部署单节点和多节点数据库,逐个元素地进行创建。我们故意采用这种“艰难的方式”来帮助你最大限度地理解如何使用 Kubernetes 原语来设置数据库所需的计算、网络和存储资源。当然,这并不代表在 Kubernetes 上生产环境中运行数据库的实际体验,原因有几个。
首先,团队通常不会一个 YAML 文件接一个 YAML 文件地手动部署数据库。那样做会变得非常繁琐。即使将配置合并到一个文件中,对于更复杂的部署来说也会变得相当复杂。想一想第 3 章中将 Cassandra 作为多节点数据库进行部署时所需的配置量,相比之下,单节点 MySQL 部署的配置就要简单得多。这种方式无法在大型企业中扩展。
其次,虽然部署数据库很重要,但如何在一段时间内保持其运行状态呢?你需要你的数据基础设施在长期内保持可靠性和高性能,而数据基础设施通常需要大量的维护和管理。换句话说,运行系统的任务通常可以分为“第一天”(将应用程序部署到生产环境中的那天)和“第二天”(之后的每一天,你需要在保持高可用性的同时操作和演进你的应用程序)。
这些关于数据库部署和运维的考虑反映了整个行业向 DevOps 发展的趋势,DevOps 是一种开发团队在支持生产环境中的应用程序时更加积极主动的方法。DevOps 实践包括使用自动化工具进行应用程序的持续集成/持续交付 (CI/CD),以缩短代码从开发者的桌面到生产环境的时间。
在本章中,我们将探讨帮助标准化数据库和其他应用程序部署的工具。这些工具采用基础设施即代码 (IaC) 的方法,使你能够以一种可以自动执行的格式来表示软件的安装和配置选项,从而减少你需要编写的配置代码总量。在接下来的两章中,我们还将强调数据基础设施的运维,并将在本书的其余部分贯穿这一主题。
使用 Helm Charts 部署应用程序
让我们首先来看一个帮助你管理配置复杂性的工具:Helm。Helm 是一个用于 Kubernetes 的包管理器,是一个开源项目,也是 CNCF 的毕业项目。包管理器的概念在多种编程语言中都很常见,例如用于 Python 的 pip、用于 JavaScript 的 Node 包管理器 (NPM) 和 Ruby 的 Gems 特性。针对特定操作系统的包管理器也存在,例如用于 Linux 的 Apt 或 macOS 的 Homebrew。正如图 4-1 所示,包管理系统的基本元素包括包、存储包的注册表以及包管理应用程序(或客户端),它帮助图表开发者注册图表,并允许图表用户在本地系统上定位、安装和更新包。
Helm 将包管理的概念扩展到 Kubernetes,并具有一些有趣的区别。如果你曾使用过之前提到的包管理器之一,你会熟悉这样一个概念:一个包包含一个二进制文件(可执行代码)以及描述该二进制文件的元数据,如其功能、API 和安装说明。在 Helm 中,这些包被称为“图表”(charts)。图表通过使用 Kubernetes 资源(如 Pods、Services 和 PersistentVolumeClaims)逐步描述如何构建 Kubernetes 应用程序。这些资源在前几章中已经介绍过,包括计算、网络和存储资源。对于计算工作负载,这些描述指向位于公共或私有容器注册表中的容器镜像。
Helm 允许图表引用其他图表作为依赖项,这为通过创建图表的集合来组合应用程序提供了一种很好的方法。例如,你可以通过定义一个 WordPress 部署的图表,并引用一个定义 MySQL 部署的图表,来定义一个类似于上一章的 WordPress/MySQL 应用程序。或者,你甚至可能找到一个定义整个 WordPress 应用程序(包括数据库)的 Helm 图表。
Kubernetes 环境先决条件
本章中的示例假设你有一个具有以下特征的 Kubernetes 集群:
- 集群应至少有三个 Worker 节点,以便展示 Kubernetes 提供的让 Pods 在集群中分布的机制。你可以使用一个名为 kind 的开源发行版在你的桌面上创建一个简单的集群。请参阅 kind 的快速入门指南,了解安装 kind 并创建多节点集群的说明。该示例代码还包含一个配置文件,你可能会发现它对创建一个简单的三节点 kind 集群很有用。
- 你还需要一个支持动态配置的 StorageClass。你可以按照“StorageClasses”中的说明,安装一个简单的 StorageClass 和提供者,以暴露本地存储。
使用 Helm 部署 MySQL
为了使内容更具体一些,我们将使用 Helm 来部署第 3 章中使用的数据库。首先,如果你的系统上还没有 Helm,你需要按照 Helm 网站上的文档进行安装。接下来,添加 Bitnami Helm 仓库:
helm repo add bitnami https://charts.bitnami.com/bitnami
Bitnami Helm 仓库包含了各种 Helm 图表,帮助你部署基础设施,如数据库、分析引擎和日志管理系统,以及电子商务、客户关系管理 (CRM) 等应用程序,还有大家熟知的 WordPress。你可以在 GitHub 上的 Bitnami Charts 仓库中找到这些图表的源代码。该仓库的 README 提供了在不同 Kubernetes 发行版中使用这些图表的有用说明。
现在,让我们使用 bitnami 仓库中提供的 Helm 图表来部署 MySQL。在 Helm 的术语中,每次部署称为一次发布 (release)。使用此图表创建的最简单的发布可能如下所示:
# 暂时不要执行我!
helm install mysql bitnami/mysql
如果你执行此命令,它将使用 Bitnami MySQL Helm 图表的默认设置创建一个名为 mysql 的发布。结果是,你将得到一个单节点的 MySQL 实例。由于你已经在第 3 章中手动部署了一个 MySQL 单节点,这次我们做一些更有趣的事情,创建一个 MySQL 集群。为此,你需要创建一个 values.yaml 文件,内容如下,或者你可以重用源代码中提供的示例:
architecture: replication
secondary:
replicaCount: 2
values.yaml 文件中的这些设置让 Helm 知道你想要使用 Bitnami MySQL Helm 图表中的选项,以复制架构的方式部署 MySQL,其中包含一个主节点和两个从节点。
MySQL Helm Chart 配置选项
如果你查看 Bitnami MySQL Helm 图表中提供的默认 values.yaml 文件,你会发现除了这里显示的简单选项外,还有相当多的配置选项。可配置的值包括以下内容:
- 要拉取的镜像及其位置
- 用于生成 PersistentVolumes 的 Kubernetes StorageClass
- 用户和管理员帐户的安全凭据
- 主节点和从节点的 MySQL 配置设置
- 要创建的从节点数量
- 存活性 (liveness) 和就绪性 (readiness) 探针的详细信息
- 亲和性 (affinity) 和反亲和性 (anti-affinity) 设置
- 使用 Pod 中断预算 (Pod disruption budgets) 管理数据库的高可用性
其中许多概念你可能已经熟悉,其他如亲和性和 Pod 中断预算等概念将在本书的后续章节中介绍。
创建好 values.yaml 文件后,你可以使用以下命令启动集群:
helm install mysql bitnami/mysql -f values.yaml
运行该命令后,你会看到来自 Helm 的安装状态以及图表中提供的说明,这些说明显示在 NOTES 下:
NAME: mysql
LAST DEPLOYED: Thu Oct 21 20:39:19 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
…
我们在这里省略了 NOTES 的内容,因为它们有点长。NOTES 中描述了建议的命令,用于监控 MySQL 初始化的状态,客户端和管理员如何连接到数据库,如何升级数据库等内容。
使用命名空间帮助隔离资源
由于我们没有指定命名空间 (Namespace),因此 Helm 发布已安装在默认的 Kubernetes 命名空间中,除非你在 kubeconfig 中单独配置了命名空间。如果你希望将 Helm 发布安装在其自己的命名空间中,以便更有效地管理其资源,你可以运行类似以下的命令:
helm install mysql bitnami/mysql \
--namespace mysql --create-namespace
这会创建一个名为 mysql 的命名空间,并在其中安装 mysql 发布。
要获取你创建的 Helm 发布的信息,可以使用 helm list 命令,该命令将生成如下输出(为了便于阅读进行了格式化):
helm list
NAME NAMESPACE REVISION UPDATED
mysql default 1 2021-10-21 20:39:19
STATUS CHART APP VERSION
deployed mysql-8.8.8 8.0.26
如果你没有将发布安装在其专用的命名空间中,仍然可以通过运行 kubectl get all 来查看 Helm 为你创建的计算资源,因为它们都已被标记为你的发布名称。初始化所有资源可能需要几分钟,但完成后,看起来会像这样:
kubectl get all
NAME READY STATUS RESTARTS AGE
pod/mysql-primary-0 1/1 Running 0 3h40m
pod/mysql-secondary-0 1/1 Running 0 3h40m
pod/mysql-secondary-1 1/1 Running 0 3h38m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT
service/mysql-primary ClusterIP 10.96.107.156 <none> ...
service/mysql-primary-headless ClusterIP None <none> ...
service/mysql-secondary ClusterIP 10.96.250.52 <none> ...
service/mysql-secondary-headless ClusterIP None <none> ...
NAME READY AGE
statefulset.apps/mysql-primary 1/1 3h40m
statefulset.apps/mysql-secondary 2/2 3h40m
正如你所见,Helm 创建了两个 StatefulSets,一个用于主副本,另一个用于从副本。mysql-primary StatefulSet 管理着一个包含主副本的 MySQL Pod,而 mysql-secondary StatefulSet 管理着两个包含从副本的 MySQL Pods。你可以使用 kubectl describe pod 命令来确定每个 MySQL 副本运行在哪个 Kubernetes Worker Node 上。
从上述输出中,你还会注意到为每个 StatefulSet 创建了两个服务,一个是无头服务(headless service),另一个具有专用的 IP 地址。由于 kubectl get all 仅告诉你有关计算资源和服务的信息,你可能还想了解存储资源。要检查这些资源,可以运行 kubectl get pv 命令。如果你安装了支持动态配置的 StorageClass,你应该会看到绑定到名为 data-mysql-primary-0、data-mysql-secondary-0 和 data-mysql-secondary-1 的 PersistentVolumeClaims 的 PersistentVolumes。
除了我们讨论的资源,安装此图表还会创建一些其他资源,我们将在接下来进行探索。
命名空间和 Kubernetes 资源范围
如果你选择将 Helm 发布安装在一个命名空间中,那么在执行大多数 kubectl get 命令时,需要指定命名空间以查看已创建的资源。例外情况是 kubectl get pv 命令,因为 PersistentVolumes 是 Kubernetes 资源之一,它们不是命名空间范围的资源;也就是说,它们可以被任何命名空间中的 Pods 使用。要了解集群中的哪些 Kubernetes 资源是命名空间范围的,哪些不是,可以运行以下命令:
kubectl api-resources
Helm 的工作原理
当你使用提供的 values 文件执行 helm install 命令时,是否好奇发生了什么?为了理解这一过程,我们来看一下 Helm 图表的内容,如图 4-2 所示。在讨论这些内容时,查看你刚刚安装的 MySQL Helm 图表的源代码也会有所帮助。
在查看 Helm 图表的内容时,你会注意到以下几个部分:
- README 文件:该文件解释了如何使用图表。这些说明会与图表一起在注册表中提供。
- Chart.yaml 文件:该文件包含有关图表的元数据,如名称、发布者、版本、关键字以及对其他图表的依赖性等属性。这些属性在搜索 Helm 注册表以查找图表时非常有用。
- values.yaml 文件:该文件列出了图表支持的可配置值及其默认值。通常,这些文件会包含大量注释,以解释可用的选项。对于 Bitnami MySQL Helm 图表,有许多可用的配置选项,如前所述。
- templates 目录:该目录包含定义图表的 Go 模板。这些模板包括一个用于生成
helm install命令执行后输出的Notes.txt文件,以及一个或多个描述 Kubernetes 资源模式的 YAML 文件。这些 YAML 文件可能会组织在子目录中(例如,用于定义 MySQL 主副本的 StatefulSet 模板)。最后,_helpers.tpl文件描述了如何使用这些模板。根据所选的配置值,有些模板可能会多次使用,有些可能根本不会使用。
当你执行 helm install 命令时,Helm 客户端会通过检查源存储库来确保其拥有你指定图表的最新副本。然后,它使用模板生成 YAML 配置代码,并用你提供的任何值覆盖图表的 values.yaml 文件中的默认值。随后,它使用 kubectl 命令将此配置应用于你当前配置的 Kubernetes 集群。
如果你想在应用配置之前查看 Helm 图表将生成的配置,可以使用便捷的 template 命令。它支持与 install 命令相同的语法:
helm template mysql bitnami/mysql -f values.yaml
运行此命令会生成相当多的输出,因此你可能希望将其重定向到文件中(在命令后附加 > values-template.yaml),以便更仔细地查看。或者,你可以查看我们在源代码库中保存的副本。
你会注意到创建了几种类型的资源,如图 4-3 所示。图中显示了许多已经讨论过的资源类型,包括用于管理主副本和从副本的 StatefulSets,每个都有其自己的服务(该图表还创建了未在图中显示的无头服务)。每个 Pod 都有自己的 PersistentVolumeClaim,并映射到唯一的 PersistentVolume。
图 4-3 还包括我们之前没有讨论过的资源类型。首先注意,每个 StatefulSet 都有一个关联的 ConfigMap,用于为其 Pods 提供一组通用的配置设置。接下来,注意名为 mysql 的 Secret,它存储了访问数据库节点暴露的各种接口所需的密码。最后,ServiceAccount 资源会应用于此 Helm 发布所创建的每个 Pod。
接下来,我们将重点讨论此部署的一些有趣方面,包括标签、ServiceAccount、Secret 和 ConfigMap 的使用。
标签 (Labels)
如果你查看 helm template 的输出,会注意到资源共享了一组常见的标签:
labels:
app.kubernetes.io/name: mysql
helm.sh/chart: mysql-8.8.8
app.kubernetes.io/instance: mysql
app.kubernetes.io/managed-by: Helm
这些标签帮助标识这些资源属于 MySQL 应用程序,并表明它们由 Helm 使用特定的图表版本进行管理。这些标签对于选择资源非常有用,尤其是在定义其他资源的配置时。
ServiceAccounts
Kubernetes 集群在访问控制方面区分了人类用户和应用程序。ServiceAccount 是 Kubernetes 资源,表示应用程序及其被允许访问的内容。例如,ServiceAccount 可能被赋予对 Kubernetes API 的某些部分的访问权限,或者访问一个或多个包含机密信息(如登录凭证)的 Secret。此功能在你的 Helm 安装 MySQL 时用于在 Pod 之间共享凭证。
在 Kubernetes 中创建的每个 Pod 都分配有一个 ServiceAccount。如果你没有指定,默认的 ServiceAccount 会被使用。安装 MySQL Helm 图表时,会创建一个名为 mysql 的 ServiceAccount。你可以在生成的模板中看到该资源的规范:
apiVersion: v1
kind: ServiceAccount
metadata:
name: mysql
namespace: default
labels: ...
annotations:
secrets:
- name: mysql
如你所见,这个 ServiceAccount 可以访问一个名为 mysql 的 Secret,我们稍后会讨论。ServiceAccount 还可以包含一种称为 imagePullSecret 的额外 Secret 类型。当应用程序需要使用来自私有注册表的镜像时,会使用这些 Secret。
默认情况下,ServiceAccount 不具备对 Kubernetes API 的访问权限。为了给这个 ServiceAccount 提供所需的访问权限,MySQL Helm 图表创建了一个 Role 来指定 Kubernetes 资源和操作,并通过 RoleBinding 将 ServiceAccount 与该 Role 关联。我们将在第 5 章讨论 ServiceAccount 和基于角色的访问控制。
Secrets
正如你在第 2 章中了解到的,Secret 提供了对你需要保密的信息的安全访问。在你的 mysql Helm 发布中包含了一个名为 mysql 的 Secret,其中包含 MySQL 实例本身的登录凭证:
apiVersion: v1
kind: Secret
metadata:
name: mysql
namespace: default
labels: ...
type: Opaque
data:
mysql-root-password: "VzhyNEhIcmdTTQ=="
mysql-password: "R2ZtNkFHNDhpOQ=="
mysql-replication-password: "bDBiTWVzVmVORA=="
这三个密码代表不同类型的访问:mysql-root-password 提供对 MySQL 节点的管理员访问权限,而 mysql-replication-password 用于节点之间的数据复制通信。mysql-password 则供客户端应用程序使用,以访问数据库进行读写数据。
ConfigMaps
Bitnami MySQL Helm 图表创建了 Kubernetes ConfigMap 资源,用于表示在运行 MySQL 主副本和从副本节点的 Pod 中使用的配置设置。ConfigMaps 以键值对的形式存储配置数据。例如,Helm 图表为主副本创建的 ConfigMap 如下所示:
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-primary
namespace: default
labels: ...
data:
my.cnf: |-
[mysqld]
default_authentication_plugin=mysql_native_password
...
在这个例子中,键是 my.cnf,表示一个文件名,值则是多行的配置设置,表示一个配置文件的内容(我们在此进行了简略处理)。接下来,查看主副本 StatefulSet 的定义。注意,ConfigMap 的内容根据 StatefulSet 的 Pod 规范被挂载为每个模板中的只读文件(同样,我们省略了一些细节以集中关注关键部分):
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql-primary
namespace: default
labels: ...
spec:
replicas: 1
selector:
matchLabels: ...
serviceName: mysql-primary
template:
metadata:
annotations: ...
labels: ...
spec:
...
serviceAccountName: mysql
containers:
- name: mysql
image: docker.io/bitnami/mysql:8.0.26-debian-10-r60
volumeMounts:
- name: data
mountPath: /bitnami/mysql
- name: config
mountPath: /opt/bitnami/mysql/conf/my.cnf
subPath: my.cnf
volumes:
- name: config
configMap:
name: mysql-primary
将 ConfigMap 挂载为容器中的卷,会在挂载目录中创建一个只读文件,该文件按键命名,内容为值。在我们的示例中,将 ConfigMap 挂载到 Pod 的 mysql 容器中会在 /opt/bitnami/mysql/conf/my.cnf 路径下创建文件。
这是在 Kubernetes 应用程序中使用 ConfigMaps 的几种方式之一:
- 正如 Kubernetes 文档所述,你可以选择将配置数据存储为更精细的键值对,这也使得在应用程序中更容易访问单个值。
- 你还可以将单个键值对引用为传递给容器的环境变量。
- 最后,应用程序可以通过 Kubernetes API 访问 ConfigMap 的内容。
更多配置选项
现在你已经使用 Helm 成功部署了一个 MySQL 集群,你可以将应用程序(例如 WordPress)指向这个集群。为什么不试试看能否将第 3 章中的 WordPress 部署调整为指向你在这里创建的 MySQL 集群呢?
为了进一步学习,你还可以将你得到的配置与 Bitnami 的 WordPress Helm 图表生成的配置进行比较。Bitnami 的 WordPress Helm 图表使用的是 MariaDB 而不是 MySQL,但除此之外,二者非常相似。
更新 Helm 图表
如果你在生产环境中运行一个 Helm 发布版本,那么很可能需要随着时间的推移对其进行维护。你可能需要更新 Helm 发布版本的原因有很多:
- 一个新的图表版本可用。
- 你的应用程序所使用的镜像有新版本可用。
- 你想更改选项的配置。
要检查是否有新的图表版本,你可以执行 helm repo update 命令。该命令不带任何选项时,会检查你已在 Helm 客户端中配置的所有图表存储库的更新:
helm repo update
运行结果如下:
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈
接下来,你可能需要对配置的值进行更新。如果你要升级到一个新的图表版本,请务必查看发布说明和可配置值的文档。建议在应用升级之前先进行测试。--dry-run 选项允许你进行此操作,产生的输出与 helm template 命令类似:
helm upgrade mysql bitnami/mysql -f values.yaml --dry-run
使用覆盖配置文件
在升级时,你可以使用一个有用的选项,即在一个新的配置文件中指定你想要覆盖的值,并同时应用新的和旧的配置文件,如下所示:
helm upgrade mysql bitnami/mysql \
-f values.yaml -f new-values.yaml
配置文件会按照它们在命令行中出现的顺序应用,因此,如果你使用这种方法,请确保覆盖的配置文件出现在原始配置文件之后。
一旦你应用了升级,Helm 将开始其工作,只更新配置更改影响的发布资源。如果你对 StatefulSet 的 Pod 模板进行了更改,这些 Pod 将根据为 StatefulSet 指定的更新策略重新启动,正如我们在“StatefulSet 生命周期管理”中讨论的那样。
卸载 Helm Charts
当你不再需要使用 Helm 发布时,可以通过名称将其卸载:
helm uninstall mysql
需要注意的是,Helm 不会移除为此 Helm 图表创建的 PersistentVolumeClaims 或 PersistentVolumes,这与我们在第 3 章中讨论的 StatefulSets 的行为一致。
其他部署工具:Kustomize 和 Skaffold
除了 Helm,Kubernetes 生态系统中还有其他工具可以帮助你管理应用程序的配置和部署,例如 Kustomize 和 Skaffold。
Kustomize 是一个用于 Kubernetes 的配置管理工具。与包管理器不同,Kustomize 不提供注册表,而是专注于帮助你为不同环境管理 Kubernetes 配置 YAML 文件。Kustomize 使用基于模板的方法,你可以创建称为覆盖(overlays)的配置代码片段,这些片段旨在覆盖基础 YAML 文件的某些部分。这些覆盖通常用于不同的环境,如开发、测试和生产,或者用于隔离特定于不同 Kubernetes 提供商的配置,其效果类似于 Helm 的 values.yaml 文件。要覆盖的部分通过 Kubernetes 标签或注释等选择器进行标识。你需要提供一个 kustomization.yaml 文件来描述模板与选择器的映射关系。当你想要定制的 YAML 文件结构良好并使用了标签或注释时,Kustomize 的效果最佳。
Skaffold 是一个自动化应用程序部署的工具,专门用于开发环境。你可以从命令行命令式地执行 Skaffold,或者作为守护进程,它会监视代码更改并构建如容器镜像等工件。当检测到相关更改时,守护进程会根据你在 skaffold.yaml 文件中定义的工作流程自动执行操作。工作流程可以包括构建和标记镜像、更新 Helm 图表或常规 Kubernetes 配置文件,以及使用 kubectl、Helm 或 Kustomize 部署应用程序等操作。
使用 Helm 部署 Apache Cassandra
现在让我们转变一下方向,看看如何使用 Helm 部署 Apache Cassandra。在本节中,你将使用 Bitnami 提供的另一个图表,因此无需添加其他仓库。你可以在 GitHub 上找到该图表的实现。Helm 提供了一种快速查看此图表元数据的方法:
helm show chart bitnami/cassandra
在查看完元数据后,你还可以了解可配置的值。你可以在 GitHub 仓库中查看 values.yaml 文件,或者使用 show 命令的另一个选项:
helm show values bitnami/cassandra
这个图表的选项列表比 MySQL 图表的列表要短,因为 Cassandra 没有主副本(primary and secondary replicas)的概念。不过,你仍然会看到类似的选项,如镜像、StorageClasses、安全性、存活性和就绪性探针等等。一些配置选项是 Cassandra 独有的,比如与 JVM 设置和种子节点(seed nodes)相关的选项(如第 3 章所讨论的那样)。
这个图表的一个有趣功能是能够从 Cassandra 节点导出指标。如果你设置 metrics.enabled=true,该图表会在每个 Cassandra Pod 中注入一个辅助容器,该容器暴露出一个端口,Prometheus 可以对其进行抓取。其他与指标相关的配置选项包括导出哪些指标、收集频率等。虽然我们在此不会使用此功能,但指标报告是管理数据基础设施的关键部分,我们将在第 6 章中详细讨论。
对于一个简单的三节点 Cassandra 配置,你可以将副本数设置为 3,并将其他配置值保持为默认值。不过,由于你只需要覆盖一个配置值,这是利用 Helm 支持在命令行上设置值的好时机,而不是提供一个 values.yaml 文件:
helm install cassandra bitnami/cassandra --set replicaCount=3
如前所述,你可以使用 helm template 命令在安装之前检查配置,或者查看我们在 GitHub 上保存的文件。不过,既然你已经创建了发布版本,你也可以使用以下命令:
helm get manifest cassandra
查看 YAML 中的资源,你会看到已经建立了一组类似的基础设施,如图 4-4 所示。
该配置包括以下内容:
- 一个引用了 Secret 的 ServiceAccount,其中包含 Cassandra 管理员帐户的密码。
- 一个 StatefulSet,使用无头服务(headless Service)来引用其 Pod。这些 Pod 均匀分布在可用的 Kubernetes 工作节点上,我们将在下一节中讨论。该服务暴露了用于节点间通信的 Cassandra 端口(7000 端口,7001 端口用于通过 TLS 进行安全通信)、通过 JMX 进行管理的端口(7199 端口)以及通过 CQL 进行客户端访问的端口(9042 端口)。
这段配置代表了一个简单的Cassandra拓扑结构,其中所有三个节点都在同一个数据中心和机架中。这种简单的拓扑反映了这个图表的一个限制——它不能创建一个由多个数据中心和机架组成的Cassandra集群。要创建更复杂的部署,你需要安装多个Helm发布,使用相同的clusterName(在这种情况下,你使用的是默认名称cassandra),但每个发布使用不同的数据中心和机架。你还需要获取第一个数据中心中的几个节点的IP地址,以便在为其他机架配置发布时作为additionalSeeds使用。
亲和性和反亲和性
如图4-4所示,Cassandra节点均匀地分布在你的集群中的工作节点上。要验证你自己的Cassandra发布是否如此,你可以运行以下命令:
kubectl describe pods | grep "^Name:" -A 3
输出示例:
Name: cassandra-0
Namespace: default
Priority: 0
Node: kind-worker/172.20.0.7
--
Name: cassandra-1
Namespace: default
Priority: 0
Node: kind-worker2/172.20.0.6
--
Name: cassandra-2
Namespace: default
Priority: 0
Node: kind-worker3/172.20.0.5
如你所见,每个Cassandra节点都运行在不同的工作节点上。如果你的Kubernetes集群有至少三个工作节点且没有其他负载,你可能会观察到类似的行为。虽然这种均匀分配可能会在负载均衡的集群中自然发生,但在你的生产环境中可能并非如此。然而,为了最大限度地提高数据的可用性,我们希望尽可能遵循Cassandra架构的初衷,将节点运行在不同的机器上以促进高可用性。
为了帮助保证这种隔离,Bitnami Helm图表使用了Kubernetes的亲和性功能,特别是反亲和性。如果你检查生成的Cassandra StatefulSet配置,你会看到以下内容:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cassandra
namespace: default
labels: ...
spec:
...
template:
metadata:
labels: ...
spec:
...
affinity:
podAffinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: cassandra
app.kubernetes.io/instance: cassandra
namespaces:
- "default"
topologyKey: kubernetes.io/hostname
weight: 1
nodeAffinity:
如上所示,Pod模板规格列出了三种可能的亲和性类型,只有podAntiAffinity被定义。这些概念是什么意思呢?
- Pod 亲和性:希望Pod调度到一个特定Pod已经运行的节点上。例如,可以使用Pod亲和性将Web服务器与其缓存放置在同一个节点上。
- Pod 反亲和性:与Pod亲和性相反,即希望Pod不要调度到一个特定Pod已经运行的节点上。这就是在这个示例中使用的约束。
- 节点亲和性:希望Pod在具有特定特征的节点上运行。
每种亲和性类型可以表示为硬约束或软约束。这些被称为requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution。第一个约束指定了在Pod调度到节点之前必须满足的规则,而第二个指定了调度程序将尝试满足的偏好,但如果有必要可以放宽,以便调度Pod。
IgnoredDuringExecution意味着这些约束只在Pod首次调度时适用。将来会添加新的RequiredDuringExecution选项,称为requiredDuringSchedulingRequiredDuringExecution,要求Kubernetes驱逐(即移动到另一个节点)不再满足条件的Pods,例如由于标签的变化。
查看上述示例,Cassandra StatefulSet的Pod模板规格使用了应用于每个Cassandra Pod的标签的反亲和性规则。最终效果是Kubernetes会尝试将Pods分布在可用的工作节点上。
这些就是查看Bitnami Helm图表中Cassandra的重点。为了清理环境,卸载Cassandra发布:
helm uninstall cassandra
如果你不再想使用Bitnami Helm图表,你也可以从Helm客户端中移除该仓库:
helm repo remove bitnami
更多 Kubernetes 调度约束
Kubernetes 支持额外的机制,以向其调度器提供关于 Pod 放置的提示。其中之一是 NodeSelectors,它与节点亲和性非常相似,但语法不如节点亲和性那样丰富,只能通过 AND 逻辑匹配一个或多个标签。由于你可能没有权限在集群中的工作节点上附加标签,Pod 亲和性通常是更好的选择。污点(Taints)和容忍(Tolerations)是另一种机制,可以用来配置工作节点,防止特定的 Pods 被调度到这些节点上。
通常,你需要谨慎地了解你对 Kubernetes 调度器施加的所有约束,以避免过度限制其放置 Pods 的能力。有关调度约束的更多信息,请参阅 Kubernetes 文档。我们还会在“ Kubernetes 的替代调度器”中探讨 Kubernetes 如何允许你插件不同的调度器。
Helm、CI/CD 和运营
Helm 是一个强大的工具,专注于一个主要任务:将复杂的应用程序部署到 Kubernetes 集群中。为了从 Helm 中获得最大收益,你需要考虑它如何融入到你的 CI/CD 工具链中:
- 自动化服务器:如 Jenkins,自动根据称为作业的脚本构建、测试和部署软件。这些作业通常基于预定义的触发器运行,例如对源代码库的提交。可以在作业中引用 Helm 图表,以在 Kubernetes 集群中安装待测试的应用程序及其支持基础设施。
- 基础设施即代码(IaC)自动化工具:如 Terraform,允许你定义模板和脚本,描述如何在各种云环境中创建基础设施。例如,你可以编写一个 Terraform 脚本,自动创建特定云提供商中的新 VPC 以及在该 VPC 中创建新的 Kubernetes 集群。然后,该脚本可以使用 Helm 在 Kubernetes 集群中安装应用程序。
虽然这些工具提供的功能确实有重叠,但你需要考虑每个工具的优势和局限性,以构建你的工具链。因此,我们需要注意的是,Helm 在管理其部署的应用程序的运营方面存在局限性。为了深入了解涉及的挑战,我们与一位构建 Helm 图表组合以管理复杂数据库部署的实践者进行了交谈。这一讨论开始介绍了 Kubernetes 自定义资源定义(CRD)和操作员模式等概念,我们将在第5章中深入探讨这些内容。
将 Helm 推向极限
作者:John Sanda,DataStax 软件工程师
K8ssandra 是一个基于 Kubernetes 的 Apache Cassandra 分发版,由多个开源组件构建而成,包括 Cassandra 操作员(Cass Operator)、用于管理反熵修复的操作工具(Reaper)和备份工具(Medusa)。K8ssandra 还包括用于指标收集和报告的 Prometheus-Grafana 堆栈。
从一开始,我们就使用 Helm 来帮助管理这些组件的安装和配置。Helm 使我们能够迅速启动项目,并吸引了 Cassandra 社区中的开发人员,他们不一定具有丰富的 Kubernetes 专业知识和经验。许多人发现,像 Helm 这样的包管理工具和安装器很容易上手。
随着项目的发展,我们开始遇到一些 Helm 的局限性。虽然安装 K8ssandra 集群的过程相对简单,但在升级和管理集群时,我们遇到了更多问题:
-
编写复杂逻辑
Helm 对控制流(如循环和 if 语句)提供了良好的支持。然而,当嵌套层次越来越深时,代码的可读性和推理难度增加,缩进也成为问题。特别是,我们发现审查 Helm 图表的更改变得相当困难。 -
重用和扩展性
Helm 变量的作用范围限于声明它们的模板,这意味着我们必须在多个模板中重新创建相同的变量。这阻碍了我们遵循“不要重复自己”(DRY)原则,结果导致了缺陷的产生。同样,虽然 Helm 有一个庞大的帮助模板函数库,但该库并不能涵盖所有用例,也没有定义自定义函数的接口。你可以定义自己的自定义模板,这允许很大的重用,但这并不能替代函数的作用。
-
项目结构和继承
当我们尝试实现 Helm 的最佳实践之一——伞形图表设计模式时,也遇到了困难。我们能够创建一个顶级的 K8ssandra Helm 图表,其中包含 Cassandra 和 Prometheus 的子图表,但在创建额外的子图表时遇到了变量作用域的问题。我们的意图是在顶级图表中定义认证设置,并将其推送到子图表,但 Helm 继承模型不支持这种功能。 -
自定义资源管理
Helm 可以创建 Kubernetes 自定义资源,但它不管理这些资源。这是 Helm 3 开发者做出的一个刻意设计选择。由于自定义资源的定义是集群范围的,如果多个 Helm 安装尝试使用不同版本的 CRD,可能会造成混淆。这在 Helm 中管理类似 Cassandra 数据中心的资源更新时给我们带来了困难。解决方法是实现标记为预升级钩子的自定义 Kubernetes 作业,由 Helm 在升级时执行。有时,这些作业的编写感觉像是在编写操作员。 -
多集群部署
虽然我们在许多情况下能够解决这些 Helm 挑战,但我们路线图上的下一个主要功能是实现跨多个 Kubernetes 集群的 Cassandra 集群。我们意识到,即使没有网络配置的复杂性,这也超出了我们使用 Helm 有效实现的范围。
最终,我们意识到我们试图让 Helm 做得太多了。很容易陷入学习如何使用锤子而将一切都看作钉子的情况,但实际上你需要的是螺丝刀。然而,我们并不认为 Helm 和操作员是相互排斥的。这些是互补的方法,我们需要根据各自的优势来使用它们。我们继续使用 Helm 执行基本的安装操作,包括安装操作员和设置 Cassandra 及其他组件使用的管理员服务账户;这些是包管理工具如 Helm 最擅长的操作。
备注:这部分内容改编自文章《我们将 Helm 推向极限,然后构建了一个 Kubernetes 操作员》。
正如 John Sanda 在他的评论中提到的,Helm 是一个强大的工具,可以用于脚本化部署由多个 Kubernetes 资源组成的应用程序,但在管理更复杂的操作任务时可能效果不佳。正如你将在接下来的章节中看到的,对于数据基础设施和其他复杂应用程序,常见的模式是使用 Helm 图表部署一个操作员(operator),然后由这个操作员来管理应用程序的部署和生命周期。
总结
在本章中,你了解了像 Helm 这样的包管理工具如何帮助你管理在 Kubernetes 上部署应用程序,包括数据库基础设施。过程中,你还学习了如何使用一些额外的 Kubernetes 资源,如 ServiceAccounts、Secrets 和 ConfigMaps。现在是时候总结一下在 Kubernetes 上运行数据库的讨论了。在下一章,我们将深入探讨如何通过使用操作员模式来管理 Kubernetes 上的数据库操作。