kubernetes in action 第二版----第十章翻译
10
使用命名空间和标签组织对象
本章覆盖
• 使用命名空间将物理集群拆分为虚拟集群
• 使用标签组织对象
• 使用标签选择器对对象的子集执行操作
• 使用标签选择器将 Pod 调度到特定节点上
• 使用字段选择器根据对象的属性进行筛选
• 为对象添加额外的非身份识别信息
Kubernetes 集群通常会被多个团队使用。这些团队应该如何在同一个集群中部署对象并组织它们,以避免一个团队意外修改其他团队创建的对象?另外,对于一个部署数百个微服务的大型团队,如何组织这些微服务,使得每个团队成员,即使是新加入的成员,也能快速了解每个对象属于哪里以及其在系统中的作用?例如,一个配置映射或秘密属于哪个应用程序。这是两个不同的问题。Kubernetes 通过对象命名空间解决第一个问题,而通过对象标签解决第二个问题。在本章中,你将学习两者的相关知识。
注意 您可以在以下位置找到本章的代码文件https://github.com/luksa/kubernetes-in-action-2ndedition/tree/master/Chapter10.
10.1 将对象组织到命名空间中
假设你的组织正在运行一个由多个工程团队使用的单一 Kubernetes 集群。每个团队都会部署完整的 Kiada 应用套件以进行开发和测试。你希望每个团队只处理他们自己的应用套件实例——每个团队只想看到自己创建的对象,而不是其他团队创建的对象。这可以通过在不同的 Kubernetes 命名空间中创建对象来实现。
注意 Kubernetes 中的命名空间有助于将 Kubernetes API 对象组织到不重叠的组中。它们与 Linux 命名空间无关,Linux 命名空间有助于隔离在一个容器中运行的进程与另一个容器中的进程,正如你在第2章中所学到的。
图 10.1 利用 Kubernetes 命名空间将物理集群划分为多个虚拟集群

如前图所示,您可以使用命名空间将一个物理 Kubernetes 集群划分为多个虚拟集群。每个团队可以访问一个或多个命名空间来创建他们的对象,而不是每个人都在一个地方创建对象。因为命名空间为对象名称提供了作用域,不同团队在各自的命名空间中创建对象时可以使用相同的名称。一些命名空间可以在不同团队或个人用户之间共享。
理解何时将对象组织到命名空间中
使用多个命名空间可以将具有众多组件的复杂系统划分为由不同团队管理的较小组。它们也可以用于在多租户环境中隔离对象。例如,您可以为每个客户创建一个单独的命名空间(或命名空间组),并在该命名空间(或组)中部署该客户的整个应用程序套件。
注意 大多数 Kubernetes API 对象类型是有命名空间的,但也有少数例外。Pods、ConfigMaps、Secrets、PersistentVolumeClaims 和 Events 都是有命名空间的。而 Nodes、PersistentVolumes、StorageClasses 以及 Namespaces 本身则没有命名空间。要查看某个资源是有命名空间还是集群范围的,可以在运行 kubectl api-resources 时检查 NAMESPACED 列。
如果没有命名空间,每个集群用户都必须在对象名称前加上唯一前缀,或者每个用户都必须使用自己的 Kubernetes 集群。
图 10.2 一些 Kubernetes API 类型是有命名空间的,而其他类型则是集群范围的。

正如你将在第23章中了解到的,命名空间还为用户权限提供了作用域。用户可能有权限管理一个命名空间中的对象,但在其他命名空间中则没有。同样,与命名空间相关的资源配额将在第20章中解释。
10.1.1 列出命名空间及其包含的对象
你创建的每个 Kubernetes 集群都包含一些常见的命名空间。让我们看看它们是什么。
列出命名空间
由于每个命名空间都由 Namespace 对象表示,你可以使用 kubectl get 命令像查看其他 Kubernetes API 对象一样显示它们。要查看集群中的命名空间,请运行以下命令:

注意 命名空间的缩写是 ns。您也可以使用 kubectl get ns 来列出命名空间。
到目前为止,您一直在默认命名空间中工作。每次创建对象时,它都会在该命名空间中创建。同样,当您使用 kubectl get 命令列出对象(例如 Pod)时,该命令只会显示该命名空间中的对象。您可能会想知道其他命名空间中是否有 Pod。我们来看看。
注意 以 kube- 为前缀的命名空间是保留给 Kubernetes 系统命名空间的。
在特定命名空间中列出对象
要列出 kube-system 命名空间中的 Pod,请使用带有 --namespace 选项的 kubectl get 命令,如下所示:
提示 你也可以使用 -n 代替 --namespace。
你将在本书的后面了解更多关于这些 Pod 的内容。如果这里显示的 Pod 与你集群中的 Pod 不完全相符,也不用担心。正如命名空间的名称所示,这些是 Kubernetes 系统 Pod。通过将它们放在这个单独的命名空间中,一切都保持得整洁清晰。如果它们都在默认命名空间中,与你自己创建的 Pod 混在一起,就很难分辨哪些属于哪里,而且你可能会不小心删除系统对象。
跨所有命名空间列出对象
你可以不用在每个命名空间中单独列出对象,也可以让 kubectl 列出所有命名空间中的对象。这一次,我们不列出 Pod,而是列出集群中的所有配置映射(ConfigMap):

提示 你也可以输入 -A 来代替 --all-namespaces。
--all-namespaces 选项很方便,当你想查看集群中的所有对象(不管它们属于哪个命名空间),或者当你记不清对象所在的命名空间时,都可以使用。
10.1.2 创建命名空间
既然你已经了解了集群中的其他命名空间,现在你将创建两个新的命名空间。
使用 kubectl create namespace 创建命名空间
创建命名空间最快的方法是使用 kubectl create namespace 命令。按如下方式创建名为 kiada-test 的命名空间:
$ kubectl create namespace kiada-test1
命名空间/kiada-test1 已创建
注意 大多数对象的名称必须符合 DNS 子域名的命名规范,如 RFC 1123 所规定,即名称只能包含小写字母数字字符、连字符和点,并且必须以字母数字字符开头和结尾。命名空间同样适用此规则,但不能包含点。你刚刚创建了名为 kiada-test1 的命名空间。现在,你将使用另一种方法创建另外一个命名空间。
从清单文件创建命名空间
如前所述,Kubernetes 命名空间由 Namespace 对象表示。因此,你可以像之前那样使用 kubectl get 命令列出它们,但你也可以通过向 Kubernetes API 提交 YAML 或 JSON 清单文件来创建它们。使用这种方法创建另一个名为 kiada-test2 的命名空间。首先,创建一个名为 ns.kiada-test.yaml 的文件,其内容如下列所示。
清单 10.1 Namespace 对象的 YAML 定义

开发人员通常不会以这种方式创建命名空间,但操作员会。例如,如果你想为一套将在多个命名空间中分发的应用程序创建一组清单文件,你可以将必要的 Namespace 对象添加到这些清单中,这样所有内容都可以部署,而无需先使用 kubectl create 创建命名空间,再应用清单。在继续之前,你应该运行 kubectl get ns 再次列出所有命名空间,以查看你的集群现在是否包含你创建的两个命名空间。
10.1.3 管理其他命名空间中的对象
你现在已经创建了两个新的命名空间:kiada-test1 和 kiada-test2,但如前所述,你仍然在默认命名空间中。如果你创建一个对象(例如 pod)而没有明确指定命名空间,该对象将被创建在默认命名空间中
在特定命名空间中创建对象
要在 kiada-test1 命名空间中创建 kiada-ssl Pod 及其关联的配置映射和密钥,请运行以下命令:

现在您可以列出 kiada-test1 命名空间中的 pods、config maps 和 secrets,以确认这些对象是创建在该命名空间中,而不是默认命名空间中:

在对象清单中指定命名空间
对象清单可以在清单的元数据部分的 namespace 字段中指定对象的命名空间。当你使用 kubectl apply 命令应用清单时,对象会在指定的命名空间中创建。你无需使用 --namespace 选项指定命名空间。下面的清单示例包含与之前相同的三个对象,但在清单中指定了命名空间。
清单 10.2 在对象清单中指定命名空间

当你使用以下命令应用此清单时,pod、配置映射和密钥会在 kiada-test2 命名空间中创建:

请注意,这次您没有指定 --namespace 选项。如果您指定了,该命名空间必须与对象清单中指定的命名空间匹配,否则 kubectl 会显示如下示例中的错误:

将 kubectl 默认设置为不同的命名空间
在前两个示例中,你学习了如何在非当前 kubectl 默认使用的命名空间中创建和管理对象。你会经常使用 --namespace 选项 —— 尤其是在你想快速检查另一个命名空间的内容时。然而,你的大多数工作仍将在当前命名空间中进行。当你创建一个新命名空间后,通常会在其中运行许多命令。为了让操作更方便,你可以告诉 kubectl 切换到该命名空间。当前命名空间是当前 kubectl 上下文的一个属性,该上下文在 kubeconfig 文件中配置。
注意 你在第3章中学习了 kubeconfig 文件。
要切换到不同的命名空间,请更新当前上下文。例如,要切换到 kiada-test1 命名空间,请运行以下命令:

提示 要快速切换到不同的命名空间,你可以设置如下别名:alias kns='kubectl config set-context --current --namespace '。然后你可以使用 kns some-namespace 在命名空间之间切换。或者,你可以安装一个实现相同功能的 kubectl 插件。你可以在 https://github.com/ahmetb/kubectx 找到它。
关于在不同命名空间中创建和管理对象,没有太多额外的内容需要学习。但在结束本节之前,我需要解释 Kubernetes 在运行于不同命名空间的工作负载之间隔离的效果。
10.1.4 了解命名空间之间的(缺乏)隔离
到目前为止,您已经在不同的命名空间中创建了几个 Pod。您已经知道如何使用 --all-namespaces 选项(或简写为 -A)来列出所有命名空间中的 Pod,因此请现在执行此操作:

在命令的输出中,你应该至少看到两个名为 kiada-ssl 的 Pod。一个位于 kiada-test1 命名空间中,另一个位于 kiada-test2 命名空间中。你可能还会在默认命名空间中看到另一个名为 kiada-ssl 的 Pod,这是前几章练习中创建的。在这种情况下,你的集群中有三个同名的 Pod,由于命名空间的存在,你都能够顺利创建而不会出现问题。同一集群的其他用户也可以部署更多这类 Pod 而不会互相冲突。
理解不同命名空间中 Pod 之间的运行时隔离。
当用户在单个物理集群中使用命名空间时,就好像他们各自使用自己的虚拟集群。但这一点只在能够创建对象而不发生命名冲突的情况下成立。物理集群节点为集群中的所有用户共享。这意味着它们的 Pod 之间的隔离性与运行在不同物理集群(因此在不同物理节点上)时的隔离性不同。
图 10.3 来自不同命名空间的 Pod 可能运行在同一个集群节点上。

当在不同命名空间中创建的两个 Pod 被调度到同一个集群节点时,它们都运行在同一个操作系统内核上。虽然它们通过容器技术相互隔离,但如果某个应用突破了其容器限制或消耗了节点过多的资源,可能会影响其他应用的运行。Kubernetes 命名空间在这里不起作用。
理解命名空间之间的网络隔离
除非明确配置,否则 Kubernetes 不会提供不同命名空间中 Pod 运行的应用之间的网络隔离。一个命名空间中的应用可以与其他命名空间中的应用通信。默认情况下,命名空间之间没有网络隔离。不过,你可以使用 NetworkPolicy 对象来配置哪些命名空间中的应用可以连接到其他命名空间中的哪些应用。你将在第 25 章学习相关内容。
使用命名空间来分隔生产、预发布和开发环境?
因为命名空间并不能提供真正的隔离,所以不应使用它们将单个物理 Kubernetes 集群拆分为生产、预发布和开发环境。在不同的物理集群上托管每个环境是一种更安全的方法。
10.1.5 删除命名空间
让我们通过删除你创建的两个命名空间来结束本节关于命名空间的内容。当你删除命名空间对象时,你在该命名空间中创建的所有对象都会自动被删除。你不需要先单独删除它们。
删除kiada-test2命名空间,命令如下:

该命令会阻塞,直到命名空间中的所有内容以及命名空间本身都被删除。但是,如果您在删除完成之前中断命令并列出命名空间,您会看到命名空间的状态为“正在终止”:

我展示这个的原因是因为你最终会运行删除命令,而它永远不会完成。你可能会中断命令并检查命名空间列表,就像我在这里展示的那样。然后你会想知道为什么命名空间的终止无法完成。
诊断命名空间终止卡住的原因
简而言之,命名空间无法删除的原因是其中创建的一个或多个对象无法被删除。你可能会想:“哦,我可以用 kubectl get all 列出命名空间中的对象,看看还有哪些对象存在”,但通常这不会让你取得任何进展,因为 kubectl 不会返回任何结果。
注意 请记住,kubectl get all 命令只列出某些类型的对象。例如,它不会列出 secrets。即使该命令没有返回任何内容,也不意味着命名空间是空的。
在我所见的几乎所有命名空间卡住的情况中,问题通常是由自定义对象及其自定义控制器未处理对象的删除并从对象中移除最终保护器(finalizer)引起的。你将在第15章中更多地了解最终保护器,在第29章中了解自定义对象和控制器。这里我只是想向你展示如何弄清楚哪个对象导致命名空间卡住。提示:命名空间对象也有一个 status 字段。虽然 kubectl describe 命令通常也会显示对象的状态,但在撰写本文时,对于命名空间情况并非如此。我认为这是一个可能会在某个时候被修复的错误。在此之前,你可以通过以下方式检查命名空间的状态:

当您删除 kiada-test2 命名空间时,您不会在此示例中看到输出。此示例中的命令输出是虚构的。我强制 Kubernetes 生成它,以演示当删除过程卡住时会发生什么。如果您查看输出,您会看到命名空间中的对象都已成功标记为删除,但由于 pod 上未移除的终结器,有一个 pod仍然存在于命名空间中。暂时不要担心终结器。您很快就会学习到它们。在继续下一节之前,请先删除 kiada-test1 命名空间。
10.2 使用标签组织 pods
在本书中,您将构建并部署完整的 Kiada 应用套件,该套件由多个服务组成。到目前为止,您已经实现了 Kiada、Quote 服务和 Quiz 服务。这些服务运行在三个不同的 pods 中。伴随这些 pods 还有其他类型的对象,如配置映射、密钥、持久卷和声明。
正如你可以想象的那样,随着书籍的推进,这些对象的数量会增加。在事情失控之前,你需要开始整理这些对象,以便你和集群中的其他所有用户都能轻松地分辨出哪些对象属于哪个服务。在使用微服务架构的其他系统中,服务的数量可能会超过100个甚至更多。这些服务中的一些会被复制,这意味着会部署相同 Pod 的多个副本。此外,在某些时间点,多个版本的服务会同时运行。这会导致系统中出现数百甚至数千个 Pod。想象一下,如果你也开始在 Kiada 套件中复制并运行多个版本的 Pod。例如,假设你同时运行 Kiada 服务的稳定版和金丝雀版本。
定义 金丝雀发布是一种部署模式,其中你将应用程序的新版本与稳定版本一起部署,并仅将少量请求指向新版本,以观察其行为,然后再向所有用户全面推出。这可以防止不良版本被过多用户使用。你运行三个稳定的 Kiada 版本副本,以及一个金丝雀实例。同样,你运行三个稳定版本的 Quote 服务实例,以及一个金丝雀版本的 Quote 服务。你运行一个单独的稳定版本的 Quiz 服务。所有这些 Pod 如下图所示。
图 10.4 Kiada 应用套件的未组织 Pod

即使系统中只有九个 pods,系统图也很难理解。而且它甚至没有显示 pods 所需的其他 API 对象。显然,你需要将它们组织成更小的组。你可以将这三个服务拆分到三个命名空间中,但这并不是命名空间的真正用途。对于这种情况,更合适的机制是对象标签。
10.2.1 引入标签
标签是一个非常强大但又简单的功能,用于组织 Kubernetes API 对象。标签是附加到对象上的键值对,它允许集群中的任何用户识别对象在系统中的角色。键和值都是简单的字符串,你可以按自己的意愿指定。一个对象可以有多个标签,但标签键在该对象中必须唯一。通常在创建对象时添加标签,但你也可以稍后更改对象的标签。
使用标签为对象提供附加信息
为了说明向对象添加标签的好处,让我们以图 10.4 中所示的 Pods 为例。这些 Pods 运行三种不同的服务——Kiada 服务、Quote 服务和 Quiz 服务。此外,Kiada 和 Quote 服务背后的 Pods 运行各自应用程序的不同版本。有三个 Pod 实例运行稳定版本,另一个运行 Canary 版本。为了帮助识别每个 Pod 中运行的应用程序及其版本,我们使用 Pod 标签。Kubernetes 并不关心你向对象添加了哪些标签。你可以随意选择键和值。在此情况下,以下两个标签是有意义的:
• app 标签用于指示 Pod 属于哪个应用程序。
• rel 标签用于指示 Pod 运行的是应用程序的稳定版本还是 Canary 版本。
如下面的图所示,所有三个 kiada-xxx 和 kiada-canary Pod 的 app 标签值都设置为 kiada,因为这些 Pod 都在运行 Kiada 应用程序。rel 标签在运行稳定版本的 Pod 与运行 Canary 版本的 Pod 之间有所不同。
图 10.5 使用 app 和 rel 标签对 Pod 进行标记

插图仅显示了 Kiada 的 Pod,但可以想象将同样的两个标签添加到其他 Pod 上。有了这些标签,用户在遇到这些 Pod 时可以轻松判断 Pod 中运行的应用程序和发布类型。
理解标签如何保持对象的组织
如果你还没有意识到给对象添加标签的价值,可以考虑通过添加 app 和 rel 标签,你已经在两个维度上组织了你的 Pod(按应用程序水平组织,按发布垂直组织),如下一图所示。
图 10.6 按两个标准组织的 Kiada 套件的所有 Pod

这在开始时可能显得抽象,直到你看到这些标签如何使使用 kubectl 管理这些 Pods 变得更容易,所以我们来实际操作一下。
10.2.2 给 Pods 添加标签
本书的代码存档包含了一组清单文件,其中包含前一个示例的所有 Pods。所有稳定的 Pods 已经被标记,但试验型(canary)Pods 尚未标记。你将需要手动为它们添加标签。
设置练习
首先,创建一个名为 kiada 的新命名空间,操作如下:

像这样将 kubectl 配置为默认使用这个新命名空间:

清单文件被组织在 Chapter10/kiada-suite/ 下的三个子目录中。你可以不用单独应用每个清单,而是使用以下命令一次性应用它们所有:

你通常习惯于应用单个清单文件,但这里你使用 -f 选项来指定一个目录名称。Kubectl 会应用该目录中找到的所有清单文件。--recursive 选项会让 kubectl 在所有子目录中查找清单,而不仅仅是指定的目录。正如你所看到的,这个命令创建了几种不同类型的对象。标签有助于保持它们的组织。
在对象清单中定义标签
查看清单文件 kiada-suite/kiada/pod.kiada-001.yaml,如下所示的列表所示。看看 metadata 部分。除了 name 字段,你以前已经多次见过之外,这个清单还包含 labels 字段。它指定了两个标签:app 和 rel。
列表 10.3 带标签的 Pod

所有类型的对象都支持标签。无论对象类型如何,都可以通过在 metadata.labels 映射中指定标签来为对象添加标签。
显示对象标签
您可以通过运行 kubectl describe 命令查看特定对象的标签。如下查看 pod kiada-001 的标签:

默认情况下,kubectl get pods 命令不会显示标签,但你可以使用 --show-labels 选项显示它们。检查命名空间中所有 Pod 的标签如下:

除了使用 --show-labels 显示所有标签外,你还可以使用 --label-columns 选项(或简写 -L)显示特定标签。每个标签都会显示在它自己的列中。按照如下方式列出所有 pod 及它们的 app 和 rel 标签:

你可以看到这两个金丝雀 Pod 没有标签。让我们来添加它们。
向现有对象添加标签
要向现有对象添加标签,你可以编辑对象的清单文件,在 metadata 部分添加标签,然后使用 kubectl apply 重新应用清单。你也可以使用 kubectl edit 直接在 API 中编辑对象定义。然而,最简单的方法是使用 kubectl label 命令。使用以下命令向 kiada-canary Pod 添加 app 和 rel 标签:

现在对pod - quote-canary做同样的操作:

列出所有 Pod 并显示它们的标签,以确认所有 Pod 现在都有标签。如果你在输入之前的命令时没有注意到错误,可能是在列出 Pod 时发现的。Pod quote-canary 的 app 标签设置错误(为 kiada 而不是 quote)。我们来修复它。
修改现有对象的标签
你可以使用相同的命令来更新对象的标签。要更改你之前设置错误的标签,请运行以下命令:

为了防止意外更改现有标签的值,您必须明确告诉 kubectl 使用 --overwrite 来覆盖该标签。正确的命令如下:

再次列出 Pods 以检查所有标签是否正确。
为所有同类对象贴标签
现在假设你想在同一个命名空间中部署另一个应用套件。在执行此操作之前,给所有现有的 Pods 添加套件标签是有用的,这样你就可以区分哪些 Pods 属于一个套件,哪些属于另一个套件。运行以下命令以向命名空间中的所有 Pods 添加标签:

再次使用 --show-labels 或 -L 选项列出 Pod,以确认所有 Pod 现在都包含这个新标签。
从对象中移除标签
好吧,我撒谎了。你不会设置另一个应用程序套件。因此,套件标签是多余的。要从对象中移除标签,请使用带有标签键后加减号的 kubectl label 命令,具体如下:

要从所有其他 Pod 上移除标签,请使用 --all 而不是指定 Pod 名称:

注意 如果您将标签值设置为空字符串,标签键不会被移除。要移除它,必须在标签键后使用减号。
10.2.3 标签语法规则
虽然您可以随意为对象添加标签,但标签键和值都有一些限制。
有效的标签键
在示例中,您使用了标签键 app、rel 和 suite。这些键没有前缀,被认为是用户私有的。Kubernetes 本身应用或读取的常用标签键总是以前缀开头。这也适用于 Kubernetes 核心以外的组件使用的标签,以及其他常见的标签键。Kubernetes 使用的带前缀标签键示例是 kubernetes.io/arch。您可以在节点对象上找到它,用于标识节点使用的架构类型。

标签前缀 kubernetes.io/ 和 k8s.io/ 保留给 Kubernetes 组件使用。如果您想为您的标签使用前缀,请使用您组织的域名以避免冲突。在为标签选择键时,前缀和名称部分都适用一些语法限制。下表提供了有效和无效标签键的示例。
表 10.1 有效和无效标签键示例
| 有效标签键 | 无效标签键 |
| foo | _foo |
| foo-bar_baz | foo%bar*baz |
| example/foo | /foo |
| example/Foo | EXAMPLE/foo |
| example.com/foo | Example..com/foo |
| my_example.com/foo | |
| example.com/foo-bar | example |
| my.example.com/foo | a.very.long.prefix.over.253.characters/foo |
以下语法规则适用于前缀:
- 必须是 DNS 子域(只能包含小写字母数字字符、连字符、下划线和点)。
- 长度不得超过 253 个字符(不包括斜杠字符)。
- 必须以正斜杠结尾。
前缀必须后跟标签名称,标签名称:
- 必须以字母数字字符开头和结尾。
- 可以包含连字符、下划线和点。
- 可以包含大写字母。
- 长度不得超过 63 个字符。
有效的标签值
请记住,标签用于向对象添加标识信息。与标签键一样,标签值也必须遵循某些规则。例如,标签值不能包含空格或特殊字符。下表提供了有效和无效标签值的示例。
表 10.2 有效和无效标签值示例
| 有效标签值 | 无效标签值 |
| foo | _foo |
| foo-bar_baz | foo%bar_baz |
| FOO | value.longer.than.63.characters |
| (empty) | value with space |
标签值:
- 可以为空。
- 如果不为空,必须以字母数字字符开头。
- 只能包含字母数字字符、连字符、下划线和点。
- 不能包含空格或其他空白字符。
- 长度不得超过63个字符。
如果您需要添加不符合这些规则的值,可以将它们作为注解添加,而不是标签。您将在本章后面了解更多关于注解的信息。
10.2.4 使用标准标签键
虽然您可以随时选择自己的标签键,但有一些标准键您应该了解。其中一些由Kubernetes自身用于标记系统对象,而其他的则在用户创建的对象中已变得常用。
Kubernetes使用的知名标签
Kubernetes 通常不会向你创建的对象添加标签。然而,它确实会对系统对象(如节点和持久卷)使用各种标签,特别是当集群运行在云环境中时。下表列出了一些你可能会在这些对象上看到的知名标签。
表 10.3 节点和持久卷上的知名标签
| 标签值 | 示例值 | 应用 | 描述 |
| Kubernetes.io/arch | Amd64 | Node | |
| Kubernetes.io/os | Linux | Node | |
| Kubernetes.io/hostname | Worker-node2 | Node | |
| Topology.kubernetes.io/ region | Eu-west3 | Node PersistentVolume | |
| Topology.kubernetes.io/ zone | Eu-west3-c | Node PersistentVolume | |
| Node.kubernetes.io/ instance-type | Micro-1 | Node |
注意 除了 kubernetes.io,你还可以在旧的前缀 beta.kubernetes.io 下找到其中一些标签。
云提供商可以为节点和其他对象提供额外的标签。例如,Google Kubernetes Engine 会添加标签 cloud.google.com/gke-nodepool和cloud.google.com/gke-osdistribution,以提供有关每个节点的更多信息。你也可以在其他对象上找到更多标准标签。
已部署应用组件的推荐标签
Kubernetes 社区已就一组标准标签达成一致,你可以将这些标签添加到你的对象中,以便其他用户和工具理解它们。以下表格列出了这些标准标签。
表 10.4 Kubernetes 社区使用的推荐标签
| 标签 | 示例 | 描述 |
| app.kubernetes.io/name | ouotes | 应用程序的名称。如果应用程序由多个组件组成,这里指的是整个应用程序的名称,而不是各个组件的名称。 |
| app.kubernetes.io/instance | ouotes-foo | 此应用程序实例的名称。如果您为不同目的创建同一应用程序的多个实例,此标签可以帮助您区分它们。 |
| app.kubernetes.io/component | database | 该组件在应用架构中所扮演的角色。 |
| app.kubernetes.io/part-of | kubia-demo | 此应用程序所属的应用程序套件名称。 |
| app.kubernetes.io/version | 1.0.0 | 应用程序的版本 |
| app.kubernetes.io/manage-by | ouotes-operator | 管理此应用程序的部署和更新的工具 |
属于同一个应用实例的所有对象应具有相同的标签集。例如,该 Pod 使用的 Pod 和持久卷声明(Persistent Volume Claim)应具有前表中列出的标签的相同值。通过这种方式,任何使用 Kubernetes 集群的人都可以看到哪些组件是属于同一个整体的,哪些不是。此外,您还可以使用标签选择器通过批量操作管理这些组件,这将在下一节中进行解释。
10.3 使用标签选择器筛选对象
在前面的练习中添加到 Pod 的标签可以让您识别每个对象并理解其在系统中的位置。到目前为止,这些标签仅在列出对象时提供了额外的信息。但标签的真正力量在于,当您使用标签选择器根据标签筛选对象时,它们将显示出巨大的价值。
标签选择器允许您选择包含特定标签的某个子集的 Pod 或其他对象,并对这些对象执行操作。标签选择器是根据对象是否包含具有特定值的特定标签键来筛选对象的标准。
标签选择器有两种类型:
• 基于等式的选择器
- 基于集合的选择器
引入基于等式的选择器
基于等式的选择器可以根据特定标签的值是否等于或不等于特定值来筛选对象。例如,在我们之前的示例中,将标签选择器 app=quote 应用于所有 Pod,将选择所有 quote Pod(所有稳定实例加上金丝雀实例),如下图所示。
图 10.7 使用基于等式的选择器选择对象

同样,标签选择器 app!=quote 会选择除 quote pods 之外的所有 pods。
引入基于集合的选择器
基于集合的选择器更强大,可以让你指定:
- 某个标签必须具有的一组值;例如:app in (quiz, quote),
- 某个标签不能具有的一组值;例如:app notin (kiada),
- 对象的标签中必须存在的特定标签键;例如,要选择具有 app 标签的对象,选择器只需写 app,
- 对象的标签中不应存在的特定标签键;例如,要选择没有 app 标签的对象,选择器写 !app。
组合多个选择器
在过滤对象时,你可以组合多个选择器。要被选中,对象必须匹配所有指定的选择器。如下面图所示,选择器 app=quote,rel=canary 会选择 pod quote-canary。
图 10.8 组合两个标签选择器

当使用 kubectl 管理对象时,你会使用标签选择器,但 Kubernetes 在对象引用其他对象的子集时也会在内部使用它们。这些场景将在接下来的两个部分中介绍。
10.3.1 使用标签选择器通过 kubectl 管理对象
如果你一直在跟随本书的练习,你已经多次使用 kubectl get 命令来列出集群中的对象。当你运行此命令而未指定标签选择器时,它会打印特定类型的所有对象。幸运的是,你在命名空间中从来没有超过几个对象,所以列表从不太长。然而,在实际环境中,命名空间中可能会有数百个特定类型的对象。这时标签选择器就派上用场了。
使用标签选择器过滤对象列表
你将使用标签选择器列出在上一节中创建的 kiada 命名空间中的 Pod。让我们尝试图 10.7 中的示例,其中选择器 app=quote 用于选择仅运行 quote 应用的 Pod。要将标签选择器应用于 kubectl get,请使用 --selector 参数(或简写 -l)指定,如下所示:

只显示quote Pod。其他 Pod 会被忽略。现在,作为另一个示例,尝试列出所有金丝雀 Pod:

我们也尝试一下图 10.8 中的示例,结合两个选择器 app=quote 和 rel=canary:

只有 quote-canary Pod 的标签同时匹配两个标签选择器,所以只有这个 Pod 会被显示。作为下一个示例,试着使用基于集合的选择器。要显示所有 quiz 和 quote Pod,请使用选择器 'app in (quiz, quote)',如下所示:

如果你使用基于等号的选择器 'app!=kiada' 或基于集合的选择器 'app notin (kiada)',你会得到相同的结果。命令中的 -L app 选项会显示每个 pod 的 app 标签的值(参见输出中的 APP 列)。你还没有尝试的两个选择器是仅测试特定标签键的存在(或不存在)的选择器。如果你想尝试它们,首先使用以下命令从 quiz pod 中移除 rel 标签:

现在你可以像这样列出没有rel标签的pod:

注意 确保在 !rel 周围使用单引号,这样你的 shell 就不会解释感叹号。
要列出所有确实有 rel 标签的 pod,请运行以下命令:
$ kubectl get pods -l rel
该命令应显示除 quiz pod 之外的所有 pods。如果您的 Kubernetes 集群在云中运行并分布在多个区域或可用区,您还可以尝试列出特定类型的节点,或列出特定区域或可用区的节点和持久卷。在表 10.3 中,您可以看到在选择器中应指定哪个标签键。现在,您已经掌握了在列出对象时使用标签选择器。您是否有信心也用它们来删除对象呢?
使用标签选择器删除对象
目前系统中正在使用两个金丝雀版本。事实证明,它们的行为并不如预期,需要被终止。您可以列出系统中所有的金丝雀版本,并逐一删除它们。一种更快的方法是使用标签选择器,在一次操作中删除它们,如下图所示。
图 10.9 使用 rel=canary 标签选择器选择并删除所有金丝雀 pods

使用如下命令删除canary pods:

命令的输出显示 kiada-canary 和 quotecanary 两个 Pod 已被删除。然而,由于 kubectl delete 命令不会要求确认,因此在使用标签选择器删除对象时,应该非常小心,尤其是在生产环境中。
10.3.2 在 Kubernetes API 对象中使用标签选择器你
已经学会了如何使用标签和选择器与 kubectl 配合来组织对象和进行过滤,但选择器同样也用于 Kubernetes API 对象中。
例如,您可以在每个 Pod 对象中指定节点选择器,以指定 Pod 可以调度到哪些节点。在下一章中,将讲解 Service 对象,您将了解到需要在该对象中定义 Pod 选择器,以指定服务将转发流量到的 Pod 子集。在接下来的章节中,您将看到 Pod 选择器如何被 Deployment、ReplicaSet、DaemonSet 和 StatefulSet 等对象使用,以定义属于这些对象的 Pod 集合。
使用标签选择器将 Pod 调度到特定节点
到目前为止,你创建的所有 Pod 都是随机分布在整个集群中的。通常,Pod 被调度到哪个节点并不重要,因为每个 Pod 都会获得它所请求的精确计算资源(CPU、内存等)。此外,其他 Pod 可以访问该 Pod,而不管该 Pod 和其他 Pod 运行在哪个节点上。然而,在某些场景下,你可能希望将特定的 Pod 仅部署在特定的节点子集上。一个很好的例子是当你的硬件基础设施不统一时。如果一些工作节点使用旋转磁盘,而其他节点使用 SSD,你可能希望将需要低延迟存储的 Pod 仅调度到可以提供这种存储的节点上。
另一个例子是,如果你想将前端 Pod 安排到某些节点,而将后端 Pod 安排到其他节点。或者,如果你想为每个客户部署一组独立的应用实例,并希望出于安全原因每组实例在自己的节点集上运行。在所有这些情况下,与其将 Pod 调度到特定节点,不如让 Kubernetes 从满足所需条件的节点集里选择一个节点。通常,你会有多于一个节点满足指定条件,这样如果一个节点出现故障,运行在其上的 Pod 可以移动到其他节点上。你可以使用的机制是标签(labels)和选择器(selectors)。
将标签附加到节点上
Kiada 应用套件包括 Kiada、Quiz 和 Quote 服务。我们可以将 Kiada 服务视为前端,而将 Quiz 和 Quote 服务视为后端服务。假设你希望 Kiada pod 只调度到你专门为前端工作负载预留的集群节点上。为此,你首先需要为其中一些节点添加相应标签。首先,列出集群中的所有节点,并选择其中一个工作节点。如果你的集群仅有一个节点,就使用这个节点。

在此示例中,我选择 kind-worker 节点作为前端工作负载的节点。选择节点后,向其添加 node-role: front-end 标签,如下所示:

现在使用标签选择器列出节点,以确认这是唯一的前端节点:

如果您的集群有许多节点,您可以用这种方式标记多个节点。
将 Pod 调度到具有特定标签的节点
要将 Pod 调度到您指定的前端节点,您必须在创建 Pod 之前在 Pod 的清单中添加节点选择器。以下清单显示了 pod.kiada-front-end.yaml 清单文件的内容。节点选择器在 spec.nodeSelector 字段中指定。
清单 3.4 使用节点选择器将 Pod 调度到特定节点

在 nodeSelector 字段中,您可以指定一个或多个标签键和值,节点必须匹配这些标签才能有资格运行该 Pod。请注意,此字段仅支持指定基于相等的标签选择器。标签值必须与选择器中的值匹配。您不能在 nodeSelector 字段中使用不等或基于集合的选择器。不过,基于集合的选择器在其他对象中是支持的。当您通过使用 kubectl apply 应用清单创建上一个示例中的 Pod 时,您会看到该 Pod 被调度到您用标签 node-role: front-end 标记的节点。您可以通过使用 -o wide 选项显示 Pod,以显示 Pod 所在的节点,从而进行确认,如下所示:

您可以多次删除并重新创建 Pod,以确保它总是落在前端节点上。
注意 在第 21 章中介绍了影响 Pod 调度的其他机制。
在持久卷声明中使用标签选择器在第 8 章中,您学习了持久卷和持久卷声明。持久卷通常表示网络存储卷,而持久卷声明允许您预留其中的一个持久卷,以便在 Pod 中使用。
我当时没有提到这一点,但你可以在 PersistentVolumeClaim 对象定义中指定标签选择器,以指示 Kubernetes 应考虑哪些持久卷进行绑定。如果没有标签选择器,任何与声明中指定的容量和访问模式匹配的可用持久卷都将被绑定。如果声明指定了标签选择器,Kubernetes 还会检查可用持久卷的标签,并且只有当卷的标签与声明中的标签选择器匹配时才会绑定该声明。与 Pod 对象中的节点选择器不同,PersistentVolumeClaim 对象中的标签选择器支持基于等值和值集的选择器,并使用略有不同的语法。下面的示例显示了一个使用基于等值选择器的 PersistentVolumeClaim 对象定义,以确保绑定的卷具有标签 type: ssd。
示例 10.4 使用基于等值选择器的 PersistentVolumeClaim 定义

matchLabels 字段的行为与上一节中您了解的 Pod 对象中的 nodeSelector 字段完全相同。或者,您可以使用 matchExpressions 字段来指定更具表达力的基于集合的标签选择器。以下示例显示了一个选择器,该选择器匹配 type 标签不是 ssd 的 PersistentVolume,并且 age 标签为 old 或 very-old。
示例 10.5 在 PersistentVolumeClaim 中使用基于集合的选择器

如您在列表中所见,您可以在选择器中指定多个 matchExpressions。要匹配选择器,PersistentVolume 的标签必须匹配所有表达式。您必须为每个表达式指定 key、operator 和 values。key 是选择器应用的标签键。operator 必须是 In、NotIn、Exists 或 DoesNotExist 之一。当您使用 In 或 NotIn 操作符时,values 数组不能为空。然而,当使用 Exists 或 DoesNotExist 操作符时,必须省略它。
注意 NotIn 操作符匹配不包含指定标签的对象。因此,带有标签选择器类型 NotIn [ssd]、age In [old, very-old] 的 PersistentVolumeClaim 可以绑定到具有标签 age: old 的 PersistentVolume,即使它没有 type 标签。要更改此行为,您必须添加一个使用 Exists 操作符的额外选择器表达式。
要查看这些选择器的实际效果,首先创建在清单文件 persistent-volumes.yaml 中找到的持久卷。然后创建清单文件 pvc.ssd-claim.yaml 和 pvc.old-non-ssd-claim.yaml 中的两个声明。你可以在书籍代码归档的 Chapter10/ 目录中找到这些文件。
使用字段选择器过滤对象
Kubernetes 最初仅允许你使用标签选择器过滤对象。然后很明显,用户也希望根据其他属性过滤对象。一个例子是根据 Pod 所运行的集群节点来过滤 Pod。现在可以使用字段选择器实现这一点。与标签选择器不同,你仅在 kubectl 或其他 Kubernetes API 客户端中使用字段选择器。没有对象在内部使用字段选择器。
您可以在字段选择器中使用的字段集合取决于对象类型。metadata.name 和 metadata.namespace 字段始终受支持。与基于等式的标签选择器类似,字段选择器支持等于(= 或 ==)和不等于(!=)运算符,并且您可以通过逗号分隔来组合多个字段选择器。列出在特定节点上运行的 Pod作为使用字段选择器的示例,运行以下命令以列出 kind-worker 节点上的 Pod(如果您的集群不是使用 kind 工具配置的,则必须指定其他节点名称):

与其显示当前命名空间中的所有 Pod,不如过滤器只选择 spec.nodeName 字段设置为 kind-worker 的那些 Pod。你怎么知道在选择器中使用哪个字段呢?当然是通过使用 kubectl explain 查找字段名称。你在第 4 章中已经学过。例如:kubectl explain pod.spec 显示 Pod 对象中 spec 部分的字段。它不会显示哪些字段在字段选择器中受支持,但你可以尝试使用一个字段,如果不受支持,kubectl 会告诉你。查找未运行的 Pod使用字段选择器的另一个示例是查找当前未运行的 Pod。你可以通过使用 status.phase!=Running 字段选择器来实现,如下所示:
![]()
由于你命名空间中的所有 Pod 都在运行,该命令不会产生任何结果,但在实际使用中它可能很有用,特别是如果你将其与 --all-namespaces 选项结合使用以列出所有命名空间中未运行的 Pod。完整命令如下:

10.4 为对象添加注解
给对象添加标签可以让它们更容易管理。在某些情况下,对象必须有标签,因为 Kubernetes 使用它们来识别哪些对象属于同一组。但正如本章所讲,你不能随意存储任何内容在标签值中。例如,标签值的最大长度仅为 63 个字符,并且值中完全不能包含空白字符。因此,Kubernetes 提供了一个类似于标签的功能——对象注解。
10.4.1 介绍对象注解
像标签一样,注解也是键值对,但它们不存储识别信息,也不能用于筛选对象。与标签不同,注解值可以更长(在本文撰写时最长可达 256 KB)且可以包含任意字符。
理解 Kubernetes 添加的注释
像 kubectl 以及在 Kubernetes 中运行的各种控制器这样的工具,可能会向你的对象添加注释,如果这些信息无法存储在对象的某个字段中。注释通常在引入新的 Kubernetes 功能时使用。如果某个功能需要对 Kubernetes API 进行更改(例如,需要向对象的模式中添加一个新字段),这类更改通常会推迟几个 Kubernetes 版本,直到确认该更改是合理的。毕竟,对任何 API 的更改都应该非常谨慎,因为一旦你向 API 添加了一个字段,就无法随意删除,否则会破坏所有使用该 API 的用户。
更改 Kubernetes API 需要谨慎考虑,每一次更改都必须首先在实践中得到验证。因此,通常不是直接向模式添加新字段,而是先引入一个新的对象注解。Kubernetes 社区有机会在实际中使用该功能。经过几次发布,当大家对该功能满意时,会引入新字段并弃用旧注解。随后再经过几次发布,注解会被移除。
添加自己的注解
与标签一样,您可以向对象添加自己的注解。注解的一个很好的用途是为每个 Pod 或其他对象添加描述,这样集群的所有用户都可以快速查看对象的信息,而无需到其他地方查找。例如,在对象的注解中存储创建该对象的人的姓名及其联系信息,可以大大促进集群用户之间的协作。
类似地,您可以使用注解提供有关在 Pod 中运行的应用程序的更多详细信息。例如,您可以将 Git 仓库的 URL、Git 提交哈希、构建时间戳以及类似信息附加到您的 Pod 上。您还可以使用注解添加某些工具需要用于管理或增强对象的信息。例如,将特定注解值设置为 true 可以向工具发出信号,指示它是否应处理和修改该对象。
理解注解键和值
适用于标签键的规则同样适用于注解键。更多信息,请参见第 10.2.3 节。另一方面,注解值没有特殊规则。注解值可以包含任意字符,且最长可达 256 KB。它必须是字符串,但可以包含普通文本、YAML、JSON,甚至是 Base64 编码的值。
10.4.2 向对象添加注解
像标签一样,注解可以添加到现有对象,或者包含在用于创建对象的对象清单文件中。让我们看看如何向现有对象添加注解。
设置对象注解
向现有对象添加注解的最简单方法是使用 kubectl annotate 命令。我们来看一个例子,向其中一个 pod 添加注解。你应该仍然拥有在本章前面的练习中创建的名为 kiada-front-end 的 pod。如果没有,你可以使用当前命名空间中的其他 pod 或对象。运行以下命令:

此命令将创建者注释(created-by),值为 'Marko Luksa<marko.luksa@xyz.com>',添加到 kiada-front-end Pod。
在对象清单中指定注释
您还可以在创建对象之前,将注释添加到对象清单文件中。以下示例展示了如何操作。您可以在 pod.pod-with-annotations.yaml 文件中找到该清单。

警告如果 YAML 解析器否则会将其视为字符串以外的其他东西。如果不这样做,则应用清单时将发生 cryptic 错误。例如,如果注释值是类似 123 的数字或可以解释为boolean (true、false,还有 yes 和 no 等词),将值括在引号(例如:“123”、“true”、“yes”)来避免以下错误:“unable要解码 YAML ...ReadString:期望“或n,但找到t”。
通过执行以下命令应用上一个列表中的清单命令:
$ kubectl apply -f pod.pod-with-annotations.yaml
10.4.3 检查对象的注解
与标签不同,kubectl get 命令没有提供在对象列表中显示注解的选项。要查看对象的注解,您应该使用 kubectl describe 命令,或在对象的 YAML 或 JSON 定义中查找注解。
使用 kubectl describe 查看对象的注解
要查看您创建的 pod-with-annotations Pod 的注解,请使用 kubectl describe:

在对象的 JSON 定义中显示对象的注释或者,您可以使用 jq 命令从 Pod 的 JSON 定义中提取注释:

你会注意到,在对象中还有一个额外的注解,其键为kubectl.kubernetes.io/last-applied-configuration。kubectl describe 命令不会显示它,因为它仅在 kubectl 内部使用,而且显示它会使输出过长。将来,这个注解可能会被弃用,然后被移除。如果你自己运行命令时没有看到它,也不用担心。
10.4.4 更新和删除注解
如果你想使用 kubectl annotate 命令更改现有的注解,你还必须指定 --overwrite 选项,就像更改现有对象标签时一样。例如,要更改 created-by 注解,完整命令如下:
![]()
要从对象中删除注释,请在要删除的注释键的末尾添加一个减号:
![]()
10.5 总结
本章中描述的 Kubernetes 功能将帮助您保持集群的有序,并使您的系统更易于理解。在本章中,您了解到:
- Kubernetes 集群中的对象通常被划分为多个命名空间。在一个命名空间内,对象名称必须唯一,但如果在不同的命名空间中创建对象,则可以给两个对象相同的名称。
- 命名空间允许不同的用户和团队使用同一个集群,就好像他们在使用独立的 Kubernetes 集群一样。
- 每个对象可以有多个标签。标签是帮助识别对象的键值对。通过向对象添加标签,您可以有效地将对象组织到不同的组中。
- 标签选择器允许您根据对象的标签筛选对象。您可以轻松筛选属于特定应用程序的 Pod,或根据其他任何条件进行筛选,前提是您之前已为这些 Pod 添加了适当的标签。
- 字段选择器类似于标签选择器,但它们允许您根据对象清单中的特定字段筛选对象。例如,字段选择器可用于列出运行在特定节点上的 Pod。不幸的是,您无法使用它们根据注解进行筛选。
- 您无需对每个 Pod 单独执行操作,可以使用标签选择器对匹配标签选择器的一组对象执行相同操作。标签和选择器也在某些对象类型的内部使用。您可以向节点对象添加标签,并在 Pod 中定义节点选择器,以便仅将该 Pod 调度到满足指定条件的节点上。
- 除了标签之外,您还可以向对象添加注解。注解可以包含更多的数据,并且可以包含标签中不允许的空格和其他特殊字符。注解通常用于添加供工具和集群用户使用的附加信息。它们也用于延迟对 Kubernetes API 的更改。
在下一章中,您将学习如何使用 Service 对象将流量转发到一组 Pod。
更多推荐
所有评论(0)