9

通过 ConfigMaps、Secrets 和 Downward API 进行配置

本章涵盖

    •  为容器的主进程设置命令和参数

    •  设置环境变量

    •  在config map中存储配置

    •  在secrets中存储敏感信息

    •  使用Download API向应用程序公开pod元数据时

    •  使用configMap,secret,downloadapi与投影卷

您现在已经学会了如何使用 Kubernetes 运行应用程序进程并将文件卷附加到它。在本章中,您将学习如何配置应用程序——要么在 Pod 清单本身中进行配置,要么通过引用其中的其他 API 对象进行配置。您还将学习如何将有关 Pod 本身的信息注入到其中运行的应用程序中。

注意 本章的代码文件请参见https://github.com/luksa/kubernetes-in-action-2ndedition/tree/master/Chapter09

9.1 设置命令、参数和环境变量

像常规应用程序一样,容器化应用程序可以通过命令行参数、环境变量和文件进行配置。你已经了解到,当容器启动时执行的命令通常在容器镜像中定义。该命令在容器的 Dockerfile 中使用 ENTRYPOINT 指令进行配置,而参数通常使用 CMD 指令指定。环境变量也可以通过 Dockerfile 中的 ENV 指令进行指定。如果应用程序是通过配置文件进行配置的,这些文件可以使用 COPY 指令添加到容器镜像中。你在前几章中已经看过多个这类示例。

    让我们将 kiada 应用程序改造为可以通过命令行参数和环境变量进行配置。之前版本的应用程序都监听在端口 8080。现在,这个端口可以通过 --listen-port 命令行参数进行配置。另外,应用程序将从环境变量 INITIAL_STATUS_MESSAGE 中读取初始状态消息。应用程序不仅返回主机名,现在还会返回 Pod 名称和 IP 地址,以及它运行的集群节点的名称。应用程序通过环境变量获取这些信息。你可以在本书的代码仓库中找到更新后的代码。新版本的容器镜像可在 docker.io/luksa/kiada:0.4 获得。更新后的 Dockerfile 也可以在代码仓库中找到,如下所示。

列表9.1使用几种应用程序配置方法的Dockerfile示例

将配置硬编码到容器镜像中,就等同于将其硬编码到应用程序源代码中。这并不理想,因为每次更改配置时,都必须重新构建镜像。此外,切勿在容器镜像中包含敏感的配置信息,例如安全凭证或加密密钥,因为任何有权访问镜像的人都可以轻易获取这些信息。

    相反,将这些文件存储在你挂载到容器中的卷中要安全得多。正如你在上一章中学到的,一种方法是将文件存储在持久卷中。另一种方法是使用 emptyDir 卷和一个 init 容器,从安全存储中获取文件并将其写入卷中。如果你阅读过前几章,你应该知道如何做到这一点,但还有更好的方法。在本章中,你将学习如何使用特殊的卷类型来实现相同的效果,而无需使用 init 容器。但首先,我们先学习如何在不重新创建容器镜像的情况下,更改命令、参数和环境变量。

9.1.1 设置命令和参数

在创建容器镜像时,命令及其参数是通过 Dockerfile 中的 ENTRYPOINT 和 CMD 指令来指定的。由于这两个指令都接受数组值,因此可以使用其中一个指令指定命令及其参数,也可以将它们分开指定。当容器执行时,这两个数组会被连接起来以生成完整的命令。Kubernetes 提供了两个与 Docker 的 ENTRYPOINT 和 CMD 指令类似的字段。这两个字段分别称为 command 和 args。您可以在 pod 清单的容器定义中指定这些字段。与 Docker 一样,这两个字段也接受数组值,并且容器中执行的最终命令是通过连接这两个数组生成的。

图9.1覆盖pod清单中的命令和参数

通常,你使用 ENTRYPOINT 指令来指定基本命令,而使用 CMD 指令来指定参数。这允许你在不必再次指定命令的情况下,在 Pod 清单中覆盖参数。如果你想覆盖命令,仍然可以这样做,并且可以在不覆盖参数的情况下进行。以下表格显示了这两个 Dockerfile 指令在 Pod 清单中对应的字段。

表9.1在Dockerfile和pod manifest中指定命令和参数

Dockerfile

Pod清单

描述

ENTRYPOINT

Command

在容器中运行的可执行文件。除了可执行文件之外,还可能包含参数。

    

CMD

Args

传递给使用ENTRYPOINT指令或命令字段指定的命令的附加参数。

让我们看两个设置command和args字段的示例。

设置命令

假设你想在启用 CPU 和堆分析的情况下运行 Kiada 应用程序。在 Node.JS 中,你可以通过向 node 命令传递 --cpu-prof 和 --heap-prof 参数来启用分析。你不需要修改 Dockerfile 并重建镜像,可以通过修改 Pod 清单来实现,如下所示。

列表9.2带有指定命令的容器定义

当你在清单中部署 Pod 时,会运行命令 node --cpu-prof --heap-prof app.js,而不是 Dockerfile 中指定的默认命令 node app.js。正如你在清单中看到的,command 字段就像它在 Dockerfile 中的对应字段一样,接受一个表示要执行命令的字符串数组。当数组只包含少数元素时,清单中使用的数组表示法非常合适,但当元素数量增加时,阅读起来就会变得困难。在这种情况下,你最好使用以下表示法:

    建议

YAML 解析器可能会将其解释为非字符串的值必须用引号括起来。这包括数字值,例如 1234,以及布尔值,例如 true 和 false。其他一些特殊字符串也必须加引号,否则它们也会被解释为布尔值或其他类型。这些值包括 true、false、yes、no、on、off、y、n、t、f、null 等。

设置命令参数

命令行参数可以用args字段覆盖,如下面的清单所示。

列表 9.3设置了args字段的容器定义

清单中的 Pod 清单会将 Dockerfile 中设置的默认 --listen-port 8080 参数覆盖为 --listen-port 9090。当你部署此 Pod 时,容器中执行的完整命令是 node app.js --listen-port 9090。该命令是 Dockerfile 中的 ENTRYPOINT 与 Pod 清单中的 args 字段的组合。

9.1.2 在容器中设置环境变量

容器化应用程序通常使用环境变量进行配置。就像命令和参数一样,您可以为每个pod的容器设置环境变量,如图9.2所示。

图9.2每个容器设置环境变量

注意 当我写下这些内容时,环境变量只能为每个容器单独设置。无法为整个 Pod 设置一组全局环境变量,并让其被所有容器继承。

您可以将环境变量设置为字面值,让它引用另一个环境变量,或者从外部源获取该值。让我们看看怎么做。

将字面值设置为环境变量

Kiada 应用程序的 0.4 版本会显示 Pod 的名称,该名称从环境变量 POD_NAME 中读取。它还允许您使用环境变量 INITIAL_STATUS_MESSAGE 设置状态消息。我们来在 Pod 清单中设置这两个变量。要设置环境变量,您可以在 Dockerfile 中添加 ENV 指令并重新构建镜像,但更快的方法是在 Pod 清单的容器定义中添加 env 字段,就像我在以下清单(文件 pod.kiada.env-value.yaml)中所做的那样。

列表 9.4在pod清单中设置环境变量

如列表所示,env字段接受一个值数组。数组中的每个条目指定环境变量的名称及其值。

注意 由于环境变量的值必须是字符串,因此必须将非字符串的值括在引号中,以防止YAML解析器将它们视为字符串以外的任何东西。如第9.1.1节所述,这也适用于诸如yes、no、true、false等字符串。

当你在列表中部署 Pod 并向应用程序发送 HTTP 请求时,你应当看到使用环境变量指定的 Pod 名称和状态信息。你还可以运行以下命令来检查容器中的环境变量。在以下输出中,你会找到这两个环境变量:

正如你所看到的,容器中还设置了一些其他变量。它们来自不同的来源——有些是在容器镜像中定义的,有些是由 Kubernetes 添加的,其余的则来自其他地方。虽然无法确切知道每个变量的来源,但你会学会识别其中的一些。例如,由 Kubernetes 添加的变量与 Service 对象相关,这将在第11章介绍。要确定其余变量的来源,你可以检查 pod 清单和容器镜像的 Dockerfile。

在环境变量值中使用变量引用

在前面的示例中,你为环境变量 INITIAL_STATUS_MESSAGE 设置了一个固定值,但你也可以使用语法 $(VAR_NAME) 在其值中引用其他环境变量。例如,你可以在状态消息变量中引用变量 POD_NAME,如以下清单所示,该清单显示了文件 pod.kiada.env-value-ref.yaml 的部分内容。

列表 9.5在另一个变量中引用环境变量

请注意,其中一个引用指向上面定义的环境变量 POD_NAME,而另一个引用指向在容器镜像中设置的变量 NODE_VERSION。你在之前在容器中运行 env 命令时看到了这个变量。当你部署 Pod 时,它返回的状态消息如下::

如您所见,对 NODE_VERSION 的引用无法解析。这是因为您只能使用 $(VAR_NAME) 语法来引用在同一清单中定义的变量。被引用的变量必须在引用它的变量之前定义。由于 NODE_VERSION 在 NodeJS 镜像的 Dockerfile 中定义,而不是在 Pod 清单中定义,因此无法解析。

注意 如果变量引用无法解析,则引用字符串将保持不变。

注意 当您希望变量包含字面字符串 $(VAR_NAME) 并且不希望 Kubernetes 解析它时,请使用双美元符号,例如 $$(VAR_NAME)。Kubernetes 将去掉其中一个美元符号并跳过解析该变量。

在命令和参数中使用变量引用

您不仅可以在其他变量中引用清单中定义的环境变量,还可以在上一节中所学的 command 和 args 字段中引用它们。例如,文件 pod.kiada.env-value-ref-inargs.yaml 定义了一个名为 LISTEN_PORT 的环境变量,并在 args 字段中引用了它。下列内容展示了该文件的相关部分。

列表 9.6在args字段中引用环境变量

这不是最好的例子,因为没有充分的理由使用变量引用,而不是直接指定端口号。但稍后你将学习如何从外部源获取环境变量的值。然后你可以像清单中所示那样使用引用,将该值注入到容器的命令或参数中。

引用不在清单中的环境变量

就像在环境变量中使用引用一样,你只能在 command 和 args 字段中使用 $(VAR_NAME) 语法来引用在 Pod 清单中定义的变量。例如,你不能引用在容器镜像中定义的环境变量。但是,你可以使用另一种方法。如果你通过 shell 运行命令,就可以让 shell 解析该变量。如果你使用的是 bash shell,可以通过使用 $VAR_NAME 或 ${VAR_NAME} 语法来引用变量,而不是使用 $(VAR_NAME)。例如,下面清单中的命令会正确打印 HOSTNAME 环境变量的值,即使它没有在 Pod 清单中定义,而是由操作系统初始化。你可以在文件 pod.env-var-references-in-shell.yaml 中找到此示例。

列表 9.7在shell命令中引用环境变量

设置pod的完全限定域名

既然我们谈到了 Pod 的主机名,现在是解释 Pod 的主机名和子域名可以在 Pod 清单中配置的好时机。默认情况下,主机名与 Pod 的名称相同,但你可以使用 Pod 规格中的 hostname 字段来覆盖它。你还可以设置 subdomain 字段,使 Pod 的完全限定域名(FQDN)如下所示:<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>。这只是 Pod 的内部 FQDN。如果没有额外的步骤,它无法通过 DNS 解析,这些步骤在第 11 章中有解释。你可以在文件 pod.kiada.hostname.yaml 中找到一个为 Pod 指定自定义主机名的示例 Pod。

9.2 使用config map将配置从pod中解耦

在上一节中,你学习了如何将配置直接硬编码到 Pod 配置文件中。虽然这比将配置硬编码在容器镜像中要好得多,但这仍然不是最理想的做法,因为这意味着你可能需要为每个部署 Pod 的环境创建一个单独的 Pod 配置文件版本,例如你的开发、测试或生产集群。为了在多个环境中复用相同的 Pod 定义,更好的方式是将配置与 Pod 配置文件分离。一种方法是将配置移动到 ConfigMap 对象中,然后在 Pod 配置文件中引用它。这就是你接下来要做的事。

9.2.1 ConfigMaps简介

ConfigMap 是一个 Kubernetes API 对象,它只包含一组键/值对。值可以是短字符串,也可以是大型的结构化文本块,这些文本通常出现在应用程序的配置文件中。Pod 可以引用 ConfigMap 中的一个或多个键/值条目。一个 Pod 可以引用多个 ConfigMap,多个 Pod 也可以使用同一个 ConfigMap。为了使应用程序与 Kubernetes 无关,它们通常不会通过 Kubernetes REST API 读取 ConfigMap 对象。相反,ConfigMap 中的键/值对会作为环境变量传递给容器,或者通过 configMap 卷以文件形式挂载到容器的文件系统中,如下图所示。

图9.3 pod通过环境变量和configMap卷使用配置映射。

在前一节中,您学习了如何在命令行参数中引用环境变量。您可以使用此技术将已作为环境变量暴露的配置映射条目传递到命令行参数中。无论应用程序如何使用配置映射,将配置存储在单独的对象中而不是 Pod 中,可以让您为不同的环境保持独立的配置,只需保留单独的配置映射清单并将其应用到目标环境即可。由于 Pod 通过名称引用配置映射,您可以在所有环境中部署相同的 Pod 清单,并通过使用相同的配置映射名称为每个环境保持不同的配置,如下图所示。

图9.4在不同环境中部署相同的pod清单和不同的配置映射清单

9.2.2 创建ConfigMap对象

让我们创建一个配置映射,并在pod中使用它。下面是一个简单的示例,其中配置映射包含一个条目,用于初始化kiada pod的环境变量INITIAL_STATUS_MESSAGE。

使用kubectl create configmap命令创建配置映射

与pod一样,你可以从YAML清单中创建ConfigMap对象,但更快的方法是使用kubectl create ConfigMap命令,如下所示:

注意 配置映射中的键只能由字母数字字符、破折号、下划线或点组成。不允许使用其他字符。

运行此命令会创建一个名为 kiada-config 的配置映射,其中包含一个条目。键和值是通过 --from-literal 参数指定的。除了 --from-literal,kubectl create configmap 命令还支持从文件中获取键/值对。下表解释了可用的方法。

表9.2使用kubectl create configmap创建配置映射项的选项

选项

描述

--from-literal

在配置映射中插入一个键和一个文字值。

示例:——from-literal mykey=myvalue。

--from-file

将文件的内容插入到配置映射中。其行为取决于 -- 后面的参数 ——from-file:如果只指定文件名(例如:--from-file myfile.txt),则使用文件的基本名称作为键,并使用整个文件的内容作为值。如果指定 key=file(例如:--from-file mykey=myfile.txt),则文件内容将存储在指定的键下。如果文件名表示一个目录,则目录中包含的每个文件都会作为单独的条目被包含。文件的基本名称用作键,文件的内容用作值。子目录、符号链接、设备、管道以及基本名称不是有效配置映射键的文件将被忽略。

--from-env-file

插入指定文件的每一行作为单独的条目

(例如:——from-env-file myfile.env)。该文件必须包含如下格式的行:key=value

配置映射通常包含多个条目。要创建包含多个条目的配置映射,您可以使用多个参数--from-literal、--from-file和--from-env-file,或者它们的组合。

从YAML清单创建配置映射

或者,您可以从 YAML 清单文件创建配置映射。下面的清单展示了一个名为 cm.kiada-config.yaml 的等效清单文件的内容,该文件可在代码仓库中获得。您可以使用 kubectl apply 应用此文件来创建配置映射。

列表9.8配置映射清单文件

列出配置映射并显示其内容

配置映射是Kubernetes API对象,它们与pod、节点、持久卷以及其他您目前了解的对象一起存在。您可以使用各种kubectl命令对它们执行CRUD操作。例如,你可以用以下方式列出配置映射:

$ kubectl get cm

注意 configmap的简写是cm。

你可以通过命令kubectl打印它的YAML清单来显示配置映射中的条目:

$ kubectl get cm kiada-config -o yaml

注意 由于 YAML 字段会按字母顺序输出,您会在输出的顶部找到 data 字段。

提示 要仅显示键/值对,可以将 kubectl 与 jq 配合使用。例如:kubectl get cm kiada-config -o json | jq .data。要显示给定条目的值,请使用如下命令:kubectl... | jq '.data["status-message"]'。

9.2.3向环境变量注入配置映射值

在上一节中,您创建了kiada-config配置映射。让我们把它用在kiada pod里。

注入单个配置映射项

要将单个配置映射条目注入到环境变量中,只需在环境变量定义中将 value 字段替换为 valueFrom 字段,并引用配置映射条目。以下清单显示了 Pod 清单的相关部分。完整的清单可以在文件 pod.kiada.env-valueFrom.yaml 中找到。

列表 9.9从配置映射项设置环境变量

让我来分解一下你在列表中看到的环境变量的定义。与指定变量的固定值不同,你声明该值应从配置映射中获取。配置映射的名称是通过 name 字段指定的,而 key 字段则指定该映射中的键。从此清单创建 Pod,并使用以下命令查看其环境变量:

当您通过curl或浏览器访问pod时,状态消息也应该出现在它的响应中。

将引用标记为可选

在前面的示例中,对配置映射键的引用被标记为可选,这样即使配置映射或键缺失,容器仍然可以执行。如果是这种情况,环境变量不会被设置。你可以将引用标记为可选,因为 Kiada 应用程序即使没有它也能正常运行。你可以删除配置映射,然后重新部署 Pod 以确认这一点。

注意 如果在容器定义中引用的配置映射或键缺失且未标记为可选,Pod 仍会正常调度。Pod 中的其他容器会正常启动。引用缺失配置映射键的容器会在你创建带有该键的配置映射后立即启动。

注入整个配置映射

容器定义中的 env 字段接受一个值的数组,因此你可以设置所需的多个环境变量。然而,如果你想设置的变量较多,一次指定每个变量可能会变得繁琐且容易出错。幸运的是,通过使用 envFrom 而不是 env 字段,你可以注入配置映射中的所有条目,而无需单独指定每个键。这种方法的缺点是你失去了将键转换为环境变量名称的能力,所以键必须已经具有适当的形式。你唯一可以进行的转换是为每个键添加前缀。

       例如,Kiada 应用程序会读取环境变量 INITIAL_STATUS_MESSAGE,但您在配置映射中使用的键是 statusmessage。如果您希望在使用 envFrom 字段将整个配置映射注入到 Pod 时被应用程序读取,则必须将配置映射的键更改为匹配预期的环境变量名称。我已经在 cm.kiada-config.envFrom.yaml 文件中完成了此操作。除了 INITIAL_STATUS_MESSAGE 键之外,它还包含另外两个键,以演示它们将全部被注入到容器的环境中。通过运行以下命令,将配置映射替换为文件中的版本:

pod.kiada.envFrom.yaml文件中的pod清单使用envFrom字段将整个配置映射注入到pod中。下面的清单显示了清单的相关部分。

列表 9.10使用envFrom将整个配置映射注入环境变量

与前面的示例中同时指定配置映射名称和键不同,这里只指定了配置映射名称。如果你根据此清单创建 Pod 并检查其环境变量,你会看到它包含环境变量 INITIAL_STATUS_MESSAGE 以及配置映射中定义的另外两个键。和之前一样,你可以将配置映射引用标记为可选,即使配置映射不存在,容器也能运行。默认情况下,情况并非如此。引用配置映射的容器在被引用的配置映射存在之前,是无法启动的。

注入多个配置映射

列表 9.10 显示,envFrom 字段接受一个值数组,这意味着你可以组合来自多个 ConfigMap 的条目。如果两个 ConfigMap 包含相同的键,则最后一个优先。你也可以将 envFrom 字段与 env 字段结合使用,如果你希望注入一个 ConfigMap 的所有条目和另一个 ConfigMap 的特定条目。

注意 当在 env 字段中配置环境变量时,它优先于在 envFrom 字段中设置的环境变量。

前缀键无论你注入的是单个 ConfigMap 还是多个 ConfigMap,你都可以为每个 ConfigMap 设置一个可选前缀。当它们的条目被注入到容器的环境时,前缀会被添加到每个键前,以生成环境变量名。

9.2.4 将配置映射项作为文件注入容器

环境变量通常用于向应用程序传递小型单行值,而多行值通常以文件的形式传递。配置映射(Config Map)条目还可以包含较大的数据块,这些数据块可以使用特殊的 configMap 卷类型投射到容器中。注意配置映射中可容纳的信息量由 etcd 决定,etcd 是用于存储 API 对象的底层数据存储。目前,最大大小大约为一兆字节。configMap 卷会将配置映射条目作为独立文件提供。容器中运行的进程通过读取文件内容来获取条目的值。此机制最常用于向容器传递大型配置文件,但也可以用于传递较小的值,或者与 env 或 envFrom 字段结合使用,将大型条目作为文件传递,而将其他条目作为环境变量传递。

从文件中创建配置映射项

在第4章中,你部署了带有Envoy sidecar的kiada pod,该sidecar负责处理pod的TLS流量。由于当时还没有解释卷(volumes),Envoy使用的配置文件、TLS证书和私钥都是内置在容器镜像中的。如果将这些文件存储在配置映射(config map)中并注入到容器中,会更为方便。这样你就可以在不重建镜像的情况下更新这些文件。但由于这些文件的安全考虑不同,我们必须以不同方式处理它们。让我们先关注配置文件。你已经学会了如何使用kubectl create configmap命令从字面值创建配置映射。这一次,你将不是直接在集群中创建配置映射,而是创建一个配置映射的YAML清单,以便将其与pod清单一起存储在版本控制系统中。

       与其手动编写清单文件,不如使用与直接创建对象相同的 kubectl create 命令来创建它。以下命令将为名为 kiadaenvoy-config 的配置映射创建 YAML 文件:

配置映射将包含来自命令中指定文件的两个条目。一个是 envoy.yaml 配置文件,另一个只是一些随机数据,用于演示二进制数据也可以存储在配置映射中。使用 --dry-run 选项时,该命令不会在 Kubernetes API 服务器中创建对象,而只是生成对象定义。-o yaml 选项将对象的 YAML 定义打印到标准输出,然后将其重定向到 cm.kiada-envoy-config.yaml 文件。以下清单显示了该文件的内容。

列表 9.11 包含多行值的配置映射清单

正如您在清单中看到的,二进制文件最终位于 binaryData 字段,而 envoy 配置文件则位于 data 字段,这在前面的部分您已经了解过。如果配置映射条目包含非 UTF-8 字节序列,它必须在 binaryData 字段中定义。kubectl create configmap 命令会自动确定将条目放在哪里。该字段中的值是 Base64 编码的,这是在 YAML 中表示二进制值的方式。相比之下,envoy.yaml 文件的内容在 data 字段中是清晰可见的。在 YAML 中,您可以使用管道符号和适当的缩进来指定多行值。有关更多方法,请参阅 YAML.org 上的 YAML 规范。

在创建配置映射时,注意空格的卫生

在从文件创建配置映射时,确保文件中的所有行都不包含尾随空格。如果有任何一行以空格结尾,则清单中条目的值会被格式化为带有转义换行符的引号字符串。这会使清单非常难以阅读和编辑。比较以下配置映射中两个值的格式化情况:

请注意,envoy-trailingspace.yaml 文件的第一行末尾包含一个空格。这会导致 ConfigMap 条目的显示格式不太适合人类阅读。相比之下,envoy.yaml 文件没有尾随空格,并以未转义的多行字符串形式呈现,这使得它易于阅读和就地修改。不要立即将 ConfigMap 清单文件应用到 Kubernetes 集群中。你应先创建引用该 ConfigMap 的 Pod。这样,你就可以观察当一个 Pod 指向不存在的 ConfigMap 时会发生什么。

在 Pod 中使用 configMap 卷

为了将 ConfigMap 条目作为文件在容器文件系统中可用,需要在 Pod 中定义一个 configMap 卷并将其挂载到容器中,如下所示的清单展示了 Pod 的相关部分:kiadassl.configmap-volume.yaml 文件。

清单9.12在pod中定义configMap卷

如果你已经阅读了前两章,那么在此列表中对 volume 和 volumeMount 的定义应该很清楚。正如你所看到的,volume 是一个指向 kiada-envoy-config 配置映射的 configMap volume,并且它挂载在 envoy 容器的 /etc/envoy 下。该 volume 包含与配置映射中的键匹配的 envoy.yaml 和 dummy.bin 文件。使用清单文件创建 pod 并检查其状态。你将看到如下内容:

因为 Pod 的 configMap 卷引用了一个不存在的 ConfigMap,并且该引用未标记为可选,所以容器无法运行。

将 configMap 卷标记为可选

之前,你已经了解到,如果容器包含引用不存在的 ConfigMap 的环境变量定义,那么容器在创建该 ConfigMap 之前无法启动。你也了解到了,这并不会阻止其他容器启动。那么在当前这种情况下,如果缺失的 ConfigMap 被引用在卷中,会怎么样呢?由于 Pod 的所有卷必须在 Pod 的容器启动之前完成设置,因此在卷中引用缺失的 ConfigMap 会阻止 Pod 中的所有容器启动,而不仅仅是卷挂载的那个容器。系统会生成一个事件来指示问题。你可以使用 kubectl describe pod 或 kubectl get events 命令来查看该事件,如前几章所述。

注意 可以通过在卷定义中添加一行optional: true 将 configMap 卷标记为可选。如果卷是可选的并且 config map 不存在,则不会创建卷,容器将启动而不挂载该卷。

为了使 Pod 的容器能够启动,通过应用之前创建的 cm.kiada-envoy-config.yaml 文件来创建配置映射。使用 kubectl apply 命令。完成此操作后,Pod 应该会启动,并且您应该能够通过列出 /etc/envoy 目录的内容来确认两个配置映射条目已作为文件暴露在容器中,如下所示:

只投射特定的配置映射项

Envoy 不需要 dummy.bin 文件,但可以想象它可能被另一个容器或 Pod 使用,并且你无法从 config map 中删除它。然而,让这个文件出现在 /etc/envoy 并不理想,所以我们需要做点处理。幸运的是,configMap 卷允许你指定将哪些 config map 条目投影为文件。以下清单展示了如何操作。

清单9.13指定将哪些配置映射项包含到configMap卷中

items 字段指定要包含在卷中的配置映射条目列表。每个条目必须在 path 字段中指定键和文件名。未在此列出的条目不会包含在卷中。通过这种方式,您可以为一个 Pod 使用单个配置映射,其中一些条目作为环境变量出现,其他条目作为文件出现。

在 configMap 卷中设置文件权限

默认情况下,configMap 卷中的文件权限设置为 rw-r--r--,或以八进制形式表示为 0644。

注意 如果你不熟悉 Unix 文件权限,八进制数 0644 相当于二进制系统中的 110,100,100,这对应于权限三元组 rw-, r--, r--。第一个元素指文件所有者的权限,第二个元素指所属组的权限,第三个元素指其他所有用户的权限。所有者可以读取 (r) 和写入 (w) 文件,但不能执行 (- 代替 x),而所属组和其他用户可以读取,但不能写入或执行文件 (r--)。

您可以通过在卷定义中设置 defaultMode 字段来设置 configMap 卷中文件的默认权限。在 YAML 中,该字段可以使用八进制或十进制值。例如,要将权限设置为 rwxr-----,请在 configMap 卷定义中添加 defaultMode: 0740。要为单个文件设置权限,请在项目的 key 和 path 旁边设置 mode 字段。在 YAML 清单中指定文件权限时,请务必不要忘记前导零,这表示该值是八进制。如果省略前导零,值将被视为十进制,这可能会导致文件权限与预期不符。

重要

当你使用 kubectl get -o yaml 来显示一个 Pod 的 YAML 定义时,请注意文件权限是以十进制数值表示的。例如,你会经常看到值 420。这是八进制值 0644 的十进制等值,它是默认的文件权限。在你继续设置文件权限并在容器中检查它们之前,你应该知道在 configMap 卷中找到的文件是符号链接(第 9.2.6 节解释了原因)。要查看实际文件的权限,你必须跟随这些链接,因为它们自身没有权限,并且总是显示为 rwxrwxrwx。

9.2.5 更新与删除config maps

与大多数 Kubernetes API 对象一样,您可以通过修改清单文件并使用 kubectl apply 重新应用到集群来随时更新配置映射。在开发过程中,您通常会使用一种更快捷的方法。

使用kubectl编辑API对象

当你想对 API 对象(例如 ConfigMap)进行快速修改时,可以使用 kubectl edit 命令。例如,要编辑 kiada-envoy-config 配置映射,请运行以下命令:

$ kubectl edit configmap kiada-envoy-config

这会在您默认的文本编辑器中打开对象清单,允许您直接修改对象。当您关闭编辑器时,kubectl 会将您的更改发布到 Kubernetes API 服务器。

配置 kubectl edit 使用不同的文本编辑器

您可以通过设置 KUBE_EDITOR 环境变量来告诉 kubectl 使用您选择的文本编辑器。例如,如果您想使用 nano 来编辑 Kubernetes 资源,可以执行以下命令(或者将其写入您的 ~/.bashrc 或等效文件):

export KUBE_EDITOR="/usr/bin/nano"

如果未设置 KUBE_EDITOR 环境变量,kubectl edit 将回退使用默认编辑器,通常通过 EDITOR 环境变量进行配置。

当您修改配置映射时会发生什么

当您更新配置映射时,configMap卷中的文件将自动更新。

注意 在更改配置映射之后,更新configMap卷中的文件可能需要一分钟的时间。

与文件不同,环境变量在容器运行时无法更新。不过,如果容器因某种原因重新启动(例如因崩溃或由于存活探针失败而被外部终止),Kubernetes 在为新容器设置环境变量时会使用新的配置映射值。问题在于你是否希望它这样做。

了解更新配置映射的后果

容器最重要的特性之一是它们的不可变性,这使您可以确信同一容器(或 Pod)的多个实例之间没有差异。那么,这些实例获取其配置的配置映射(Config Map)是否也应该是不可变的呢?让我们想一想。如果您更改一个用于向应用程序注入环境变量的配置映射,会发生什么?如果应用程序通过配置文件进行配置,但在文件修改后不会自动重新加载,又会怎样?您对配置映射所做的更改不会影响任何正在运行的应用程序实例。然而,如果这些实例中的某些被重启,或者您创建了新的实例,它们将使用新的配置。

即使是能够重新加载其配置的应用程序,也会发生类似的情况。Kubernetes 会异步更新 configMap 卷。一些应用程序实例可能比其他实例更早看到这些更改。而且由于更新过程可能需要几十秒,单个 Pod 实例中的文件可能会在相当长的一段时间内不同步。在这两种情况下,你都会得到配置不同的实例。这可能导致系统的某些部分的行为与其他部分不同。当你决定是否允许在正在运行的 Pod 使用配置映射时更改它们时,需要考虑到这一点。

阻止配置映射被更新

为了防止用户更改配置映射中的值,可以将配置映射标记为不可变的,如下面的清单所示。

清单9.14创建不可变配置映射

如果有人试图更改不可变 ConfigMap 中的 data 或 binaryData 字段,API 服务器会阻止这种操作。这确保了所有使用该 ConfigMap 的 Pod 都使用相同的配置值。如果你想运行一组具有不同配置的 Pod,通常会创建一个新的 ConfigMap 并将它们指向该 ConfigMap。不可变的 ConfigMap 可以防止用户意外更改应用程序配置,同时也有助于提高 Kubernetes 集群的性能。当 ConfigMap 被标记为不可变时,使用它的工作节点上的 Kubelet 不必被通知 ConfigMap 对象的更改。这可以减轻 API 服务器的负载。

删除一个 ConfigMap

可以使用kubectl delete命令删除ConfigMap对象。引用配置映射的正在运行的pod将继续不受影响地运行,但只有在它们的容器必须重新启动之前才会运行。如果容器定义中的配置映射引用没有标记为可选,则容器将无法运行。

9.2.6 了解configMap卷的工作原理

在您开始在您自己的pod中使用configMap卷之前,了解它们是如何工作的非常重要,否则您将花费大量时间与它们作斗争。您可能会认为,当您将configMap卷挂载到容器中的某个目录中时,Kubernetes只是在该目录中创建一些文件,但是事情比这更复杂。有两点需要记住。一个是卷通常是如何挂载的,另一个是Kubernetes如何使用符号链接来确保文件自动更新。

挂载卷会隐藏文件目录中的现有文件

如果你将任何卷挂载到容器文件系统中的某个目录,原本在该目录下容器镜像中的文件将无法再被访问。例如,如果你将一个 configMap 卷挂载到 /etc 目录(在 Unix 系统中,该目录包含重要的配置文件),容器中运行的应用程序将只能看到在 configMap 中定义的文件。这意味着原本应该在 /etc 中的其他所有文件都不再存在,应用程序可能无法运行。然而,可以通过在挂载卷时使用 subPath 字段来缓解这个问题。想象一下,你有一个包含 my-app.conf 文件的 configMap 卷,并且你希望将其添加到 /etc 目录而不丢失该目录中的任何现有文件。你不是将整个卷挂载到 /etc,而是使用 mountPath 和 subPath 字段的组合只挂载特定文件,如下清单所示。

清单9.15将单个文件装入容器

为了更容易理解这一切是如何工作的,请查看下图。

图9.5使用subPath从卷中挂载单个文件

subPath属性可以在挂载任何类型的卷时使用,但是当你将它与configMap卷一起使用时,请注意以下警告:

警告 如果使用subPath字段来挂载单个文件而不是整个configMap卷,则在修改配置映射时不会更新该文件。

为了解决这个问题,您可以将整个卷挂载到另一个目录中,并在所需位置创建一个指向另一个目录中的文件的符号链接。您可以事先在容器映像本身中创建这个符号链接。

ConfigMap卷使用符号链接来提供原子更新

一些应用程序监视其配置文件的更改,并在发生更改时重新加载它们。但是,如果应用程序正在使用一个大文件或多个文件,则应用程序可能会在所有文件更新完成之前检测到文件已更改。如果应用程序读取部分更新的文件,它可能无法正常工作。

为了防止这种情况,Kubernetes确保自动更新configMap卷中的所有文件,这意味着所有更新都是即时完成的。这是通过使用符号文件链接实现的,正如你可以看到的,如果你列出/etc/envoy目录中的所有文件:

正如您在列表中所看到的,作为文件投影到卷中的配置映射条目是指向目录中路径的符号链接,该目录名为 ..data,它本身也是一个符号链接。它指向一个目录,其名称明显表示时间戳。因此,应用程序读取的文件路径通过两个连续的符号链接指向实际文件。这看起来可能不必要,但它允许您原子地更新所有文件。每次更改配置映射时,Kubernetes 都会创建一个新的带时间戳的目录,将文件写入该目录,然后将 ..data 符号链接关联到这个新目录,从而瞬间替换所有文件。

注意 如果在卷挂载定义中使用subPath,则不会使用此机制。相反,该文件将直接写入目标目录,并且在修改配置映射时不会更新该文件。

9.3 使用Secrets将敏感数据传递给容器

在上一节中,您学习了如何将配置数据存储在 ConfigMap 对象中,并通过环境变量或文件将其提供给应用程序。您可能会认为也可以使用 ConfigMap 来存储敏感数据,例如凭证和加密密钥,但这并不是最佳选择。对于需要保密的数据,Kubernetes 提供了另一种对象类型——Secrets。下面将介绍 Secrets。

9.3.1 引入Secrets

秘密与配置映射非常相似。就像配置映射一样,它们包含键值对,并且可以用于将环境变量和文件注入到容器中。那么,我们为什么还需要秘密呢?Kubernetes 在添加配置映射之前就已经支持秘密了。最初,在存储明文数据时,秘密的用户体验并不友好。出于这个原因,后来引入了配置映射。随着时间的推移,秘密和配置映射都发展到支持这两种类型的值。这两种类型的对象提供的功能也趋于一致。如果它们现在才被添加,肯定会作为单一的对象类型引入。然而,由于它们是逐步演变的,因此它们之间仍存在一些差异。

配置映射和秘密字段的差异

秘密的结构与配置映射略有不同。下表显示了这两种对象类型中的字段。

表9.3秘密和配置映射结构的区别

Secret

ConfigMap

描述

Data

binaryData

键值对的映射。这些值是Base64编码的字符串。

StringData

Data

键值对的映射。值是纯文本字符串。secrets中的stringData字段是只写的。

Immutable

Immutable

一个布尔值,指示对象中存储的数据是否可以更新。

type

N/A

指示秘密类型的字符串。可以是任何字符串值,但一些内置类型有特殊要求。

正如您在表格中看到的,secrets 中的 data 字段对应于 config maps 中的 binaryData 字段。它可以包含以 Base64 编码的二进制值。secrets 中的 stringData 字段等同于 config maps 中的 data 字段,用于存储明文值。secrets 中的 stringData 字段是只写的。您可以使用它向 secret 添加明文值,而无需手动编码。当您再次读取 Secret 对象时,您添加到 stringData 的任何值都会以 Base64 编码的字符串形式包含在 data 字段中。这与 config maps 中 data 和 binaryData 字段的行为不同。无论您向这些字段中的哪个字段添加键值对,当您从 API 中读取 ConfigMap 对象时,该键值对都会保持在原字段中。

像配置映射一样,通过将 immutable 字段设置为 true,密钥也可以被标记为不可变。密钥有一个配置映射没有的字段。type 字段用于指定密钥的类型,主要用于程序化处理密钥。您可以将 type 设置为任意值,但有几种具有特定语义的内置类型。

理解内置密钥类型

当你创建一个 Secret 并将其类型设置为内置类型之一时,它必须符合该类型定义的要求,因为各种 Kubernetes 组件会使用它们,并期望它们在特定的键下包含特定格式的值。下表说明了撰写本文时存在的内置 Secret 类型。

表 9.4 Secret 类型

内置secret类型

描述

Opaque

这种类型的秘密可以包含存储在任意密钥下的秘密数据。如果创建一个没有类型字段的秘密,则创建一个不透明的秘密。

Bootstrap.kubernetes.io/token

这种类型的秘密用于启动新集群节点时使用的令牌。

Kubernetes.io/basic-auth

这种类型的秘密存储基本身份验证所需的凭据。必须包含用户名和密码密钥。

Kubernete.io/dockercfg

这种类型的秘密存储访问Docker镜像注册表所需的凭据。它必须包含一个名为。Dockercfg,其中的值是~/. cfg文件的内容。的旧版本使用的Dockercfg配置文件

码头工人。

Kubernetes.io/dockerconfigjson

与上面一样,这种类型的秘密存储访问Docker注册表的凭据,但使用较新的Docker配置文件格式。Secret必须包含一个叫.dockerconfigjson的键,值的内容~ / .docker /配置。Docker使用的json文件。

Kubernetes.io/service-account-token

这种类型的秘密存储标识Kubernetes服务帐户的令牌。您将在第23章了解服务帐户和这个令牌。

Kubernetes.io/ssh-auth

这种类型的秘密存储用于SSH身份验证的私钥。私钥必须存储在密钥ssh-privatekey下。

Kubernetes.io/tls

这种类型的密钥会存储 TLS 证书及相关的私钥。它们必须分别在密钥 tls.crt 和 tls.key 下存储在秘密中。

了解Kubernetes如何存储秘密和配置映射

除了配置映射或密钥支持的字段名称有少许差异外,Kubernetes 对它们的处理方式也不同。当涉及到密钥时,你需要记住,它们在所有 Kubernetes 组件中都以特定方式处理,以确保其安全性。例如,Kubernetes 确保密钥中的数据仅分发给运行需要该密钥的 Pod 的节点。此外,工作节点上的密钥始终存储在内存中,绝不写入物理存储。这就大大降低了敏感数据泄露的可能性。因此,重要的是,你应只将敏感数据存储在密钥中,而不是配置映射中。

9.3.2 创建一个secret

在第 9.2 节中,你使用了配置映射(config map)将配置文件注入到 Envoy sidecar 容器中。除了配置文件之外,Envoy 还需要 TLS 证书和私钥。由于私钥属于敏感数据,因此应将其存储在 secret 中。在本节中,你将创建一个 secret 来存储证书和私钥,并将其投影到容器的文件系统中。通过将配置文件、证书和密钥文件都从容器镜像外部获取,你可以用通用的 envoyproxy/envoy 镜像替换自定义的 kiada-ssl-proxy 镜像。这是一个相当大的改进,因为从系统中移除自定义镜像总是件好事,这样你就不再需要维护它们。首先,你将创建这个 secret。书籍代码仓库中提供了证书和私钥的文件,但你也可以自己创建它们。

创建 TLS 密钥

像配置映射一样,kubectl 也提供了创建不同类型密钥的命令。由于你创建的密钥将由你自己的应用程序而非 Kubernetes 使用,因此无论你创建的密钥是 Opaque 类型还是 kubernetes.io/tls 类型(如表 9.4 所述),都没有关系。然而,由于你要创建的密钥包含 TLS 证书和私钥,建议使用内置的 kubernetes.io/tls 类型密钥以实现标准化。要创建该密钥,请运行以下命令:

此命令指示 kubectl 创建一个名为 kiada-tls 的 TLS 密钥。证书和私钥将分别从文件 example-com.crt 和 example-com.key 中读取。

创建通用(不透明)密钥

或者,你可以使用 kubectl 创建一个通用密钥。生成的密钥中的项目将相同,唯一的区别是其类型。以下是创建该密钥的命令:

在这种情况下,kubectl 会创建一个通用的 Secret。examplecom.crt 文件的内容会存储在 tls.crt 键下,而 example-com.key 文件的内容会存储在 tls.key 键下。

注意 与配置映射类似,Secret 的最大大小约为 1MB。

从 YAML 清单创建 Secret

kubectl create secret 命令会直接在集群中创建 secret。之前,你已经学习了如何为 config map 创建 YAML 清单。那么 secrets 呢?显而易见,将 secrets 的 YAML 清单像处理 config map 那样存储在版本控制系统中并不是最好的做法。然而,如果你需要创建 YAML 清单而不是直接创建 secret,你仍然可以使用 kubectl create --dryrun=client -o yaml 这个方法。假设你想创建一个包含用户凭据(键为 user 和 pass)的 secret YAML 清单,你可以使用以下命令来创建该 YAML 清单:

如这里所示,使用 kubectl create 技巧创建清单比从头开始创建并手动输入 Base64 编码的凭据要容易得多。或者,您可以按照下面解释的方式使用 stringData 字段来避免对条目进行编码。

使用 stringData 字段

由于并非所有敏感数据都是二进制形式,Kubernetes 也允许您通过使用 stringData 而不是 data 字段在 Secret 中指定明文值。下面的清单展示了如何创建与前一个示例中相同的 Secret。

清单 9.16 使用 stringData 字段向 Secret 添加明文条目

stringData 字段是只写的,只能用于设置值。如果你创建这个 Secret 然后使用 kubectl get -o yaml 将其取回,stringData 字段将不再存在。相反,你在其中指定的任何条目都会显示在 data 字段中,并以 Base64 编码的值呈现。

提示 由于 Secret 中的条目总是以 Base64 编码的值表示,处理 Secret(尤其是读取它们)并不像处理 ConfigMap 那样方便,因此尽可能使用 ConfigMap。但切勿为了方便而牺牲安全性。

好了,让我们回到之前创建的 TLS Secret,并在 Pod 中使用它。

9.3.3 在容器中使用secret

如前所述,您可以在容器中使用 secrets,就像使用 config maps 一样——您可以用它们来设置环境变量或在容器的文件系统中创建文件。我们先来看后者。

使用 secret 卷将 secret 条目映射到文件中

在前面的某一节中,您创建了一个名为 kiada-tls 的 secret。现在,您将使用 secret 卷将其中包含的两个条目映射到文件中。secret 卷类似于之前使用的 configMap 卷,但它指向的是 secret,而不是 config map。要将 TLS 证书和私钥映射到 kiada-ssl Pod 的 envoy 容器中,您需要定义一个新的卷和一个新的卷挂载,如下一个清单所示,该清单包含 pod.kiada-ssl.secret-volume.yaml 文件中的重要部分。

清单 9.17 在 Pod 中使用 secret 卷

如果你已经阅读了第9.2节关于配置映射(config maps)的内容,那么本示例中卷(volume)和卷挂载(volumeMount)的定义应该很容易理解,因为它们包含的字段与使用配置映射时的字段相同。唯一的两个区别是:卷类型为 secret 而不是 configMap,并且引用的 secret 名称是在 secretName 字段中指定的,而在 configMap 卷定义中,配置映射是通过 name 字段指定的。

注意 与configMap 卷类似,你可以使用 defaultMode 和 mode 字段设置 secret 卷的文件权限。此外,如果你希望 Pod 即使在引用的 secret 不存在时也能启动,可以将 optional 字段设置为 true。如果省略该字段,Pod 将在你创建 secret 之前无法启动。

鉴于 example-com.key 文件的敏感性,mode 字段用于将文件权限设置为 0600 或 rw-------。example-com.crt 文件则使用默认权限。要说明 Pod、其 Secret 卷以及引用的 Secret 及其条目,请参见下图。

图 9.6 通过 Secret 卷将 Secret 条目投影到容器的文件系统中

读取秘密卷中的文件

在部署了前面清单中的pod之后,可以使用以下命令检查秘密卷中的证书文件:

如您所见,当您通过 secret 卷将 secret 的条目投射到容器中时,投射的文件不会进行 Base64 编码。应用程序无需对文件进行解码。如果将 secret 条目注入环境变量,也同样适用。

注意secret 卷中的文件存储在内存文件系统(tmpfs)中,因此不太可能被泄露。

将 secret 注入环境变量

与配置映射类似,您也可以将 secret 注入容器的环境变量。例如,您可以将 TLS 证书注入到 TLS_CERT 环境变量,就好像证书存储在配置映射中一样。以下清单展示了如何操作。

清单 9.18 将 secret 的键值对作为环境变量暴露

这与设置 INITIAL_STATUS_MESSAGE 环境变量并无太大不同,不同之处在于你现在通过使用 secretKeyRef 而不是 configMapKeyRef 来引用一个密钥。你也可以使用 envFrom 来注入整个密钥,而不是像在第 9.2.3 节中那样逐个注入其条目。与使用 configMapRef 不同,你将使用 secretRef 字段。

你是否应该将密钥注入到环境变量中?

正如你所看到的,将密钥注入环境变量与注入配置映射没有区别。但即使 Kubernetes 允许你以这种方式暴露密钥,这可能也不是最佳做法,因为它可能带来安全风险。应用程序通常会在错误报告中输出环境变量,甚至在启动时将其写入应用日志,如果你将密钥注入环境变量中,可能会无意中暴露这些密钥。此外,子进程会继承父进程的所有环境变量。因此,如果你的应用程序调用第三方子进程,你就无法知道密钥会流向何处。

提示 与其将密钥注入环境变量,不如将它们以文件形式投影到容器的密钥卷中。这可以减少密钥被攻击者无意中暴露的可能性。

9.4 通过Download API向应用程序传递pod元数据

到本章为止,你已经学会了如何向你的应用程序传递配置数据。但这些数据一直是静态的。这些值在你部署 Pod 之前就已知,并且如果你部署了多个 Pod 实例,它们都会使用相同的值。但是,对于在 Pod 创建并调度到集群节点之前未知的数据怎么办,例如 Pod 的 IP、集群节点的名称,甚至是 Pod 本身的名称?还有那些已经在 Pod 清单的其他地方指定的数据呢,例如分配给容器的 CPU 和内存数量?优秀的工程师从不愿重复自己。

注意 你将在第 20 章中学习如何指定容器的 CPU 和内存限制。

9.4.1 介绍 Downward API

在本书接下来的章节中,你将了解可以在 Pod 中设置的许多附加配置选项。有些情况下,你需要将相同的信息传递给你的应用程序。你可以在定义容器环境变量时重复这些信息,但更好的选择是使用所谓的 Kubernetes Downward API,它允许你通过环境变量或文件暴露 Pod 和容器的元数据。

理解 Downward API 是什么

Downward API 并不是一个你的应用程序需要调用以获取数据的 REST 端点。它只是将 Pod 的元数据、规格或状态字段中的值注入到容器中的一种方式。因此得名如下。下图展示了 Downward API 的示意图。

图 9.7 Downward API 通过环境变量或文件暴露 Pod 元数据。

正如您所看到的,这与设置环境变量或从配置映射和密钥投影文件没有什么不同,只不过这些值来自于 Pod 对象本身。

理解元数据是如何注入的

在本章前面,您已经了解到,可以使用 valueFrom 字段从外部来源初始化环境变量。要从配置映射获取值,请使用 configMapKeyRef 字段;要从密钥获取值,请使用 secretKeyRef 字段。要改为使用 Downward API 从 Pod 对象本身获取值,请根据要注入的信息使用 fieldRef 或 resourceFieldRef 字段。前者用于引用 Pod 的通用元数据,而后者用于引用容器的计算资源限制。

或者,您可以通过向 Pod 添加 downwardAPI 卷,将 Pod 的元数据以文件的形式投射到容器的文件系统中,就像添加 configMap 或 secret 卷一样。您很快就会学习如何操作,但首先让我们看看您可以注入哪些信息。

了解可注入的元数据

您不能使用 Downward API 注入 Pod 对象的任何字段。只有某些字段是受支持的。下表显示了可以通过 fieldRef 注入的字段,以及它们是否只能通过环境变量、文件或两者都可暴露。

表 9.5 通过 fieldRef 字段注入的 Downward API 字段

字段

描述

环境允许

卷允许

metadata.name

Pod的名称

metadata.namespace

Pod的命名空间

metadata.uid

Pod的Uid

metadata.lables

所有pod的标签,每行一个标签,格式为key= " value "。

metadata.labels[‘key’]

指定标签的值。

metadata.annotations

所有pod的注释,每行一个,格式为key= " value "。

metadata.annotations['key']

指定注释的值。

spec.nodeName

运行pod的工作节点的名称。

spec.serviceAccountName

pod的服务帐户名。

status.podIP

Pod的IP地址

status.hostIP

工作节点的IP地址

你可能还不熟悉大多数这些字段,但在本书剩下的章节中你会了解它们。如你所见,有些字段只能注入到环境变量中,而其他字段只能投影到文件中。有些字段两者都可以。关于容器计算资源限制的信息是通过 resourceFieldRef 字段注入的。它们都可以注入到环境变量中,也可以通过 downwardAPI 卷注入。下表列出了它们。

表 9.6 通过 resourceFieldRef 字段注入的 Downward API 资源字段

资源字段

描述

环境允许

卷允许

requests.cpu

requests.memory

requests.emphemeral-storage

limits.cpu

limits.memory

limits.ephemeral-storage

你将在第20章中学习资源请求和限制,该章解释了如何限制容器可用的计算资源。 本书的代码仓库包含文件 pod.downward-api-test.yaml,该文件定义了一个使用 Downward API 将每个支持的字段注入到环境变量和文件中的 Pod。你可以部署该 Pod,然后查看其容器日志以了解注入的内容。 接下来是 Kiada 应用中使用 Downward API 的一个实际示例。

9.4.2 将 Pod 元数据注入环境变量

在本章开始时,介绍了 Kiada 应用程序的新版本。该应用程序现在在 HTTP 响应中包含了 Pod 和节点的名称及其 IP 地址。您将通过 Downward API 将这些信息提供给应用程序。

注入 Pod 对象字段

应用程序期望通过环境变量分别传递 Pod 的名称和 IP,以及节点的名称和 IP,变量名称为 POD_NAME、POD_IP、NODE_NAME 和 NODE_IP。您可以在 pod.kiada-ssl.downward-api.yaml 文件中找到一个使用 Downward API 将这些变量提供给容器的 Pod 清单。该文件的内容如下所示。

清单9.19 使用Download API设置环境变量

在您创建此 Pod 之后,您可以使用 kubectl logs 查看其日志。应用程序在启动时会打印三个环境变量的值。您也可以向应用程序发送请求,应该会收到如下响应:

通过运行命令 kubectl get po kiada-ssl -o yaml,将响应中的值与 Pod 对象 YAML 定义中的字段值进行比较。或者,你也可以将它们与以下命令的输出进行比较:

还可以通过运行kubectl exec kiada-ssl——env来检查容器的环境。

注入容器资源字段

即使你还没有学会如何限制容器可用的计算资源,我们先快速了解一下如何在应用需要时将这些约束传递给它。第20章介绍了如何设置容器可使用的CPU核心数和内存量。这些设置被称为CPU和内存资源限制。Kubernetes确保容器不能使用超过分配的资源量。某些应用程序需要知道分配给它们的CPU时间和内存,以便在给定的约束下优化运行。这也是Downward API的一个用途。下面的示例展示了如何通过环境变量暴露CPU和内存限制。

示例9.20 使用Downward API注入容器的计算资源限制

每个 resourceFieldRef 也可以指定一个除数。它用于指定值所使用的单位。在示例中,除数设置为 1k。这意味着内存限制值会除以 1000,然后结果存储在环境变量中。因此,环境变量中的内存限制值将以千字节为单位。如果你没有指定除数,就像示例中 MAX_CPU_CORES 变量定义的情况一样,值默认为 1。内存限制/请求的除数可以是 1(字节)、1k(千字节)或 1Ki(千二进制字节)、1M(兆字节)或 1Mi(兆二进制字节)等。CPU 的默认除数是 1,即一个完整核心,但你也可以将其设置为 1m,即一个毫核心,或核心的千分之一。

由于环境变量是在容器定义中定义的,因此默认情况下使用的是包含容器的资源限制。在某些情况下,如果容器需要知道 Pod 中另一个容器的资源限制,可以在 resourceFieldRef 中使用 containerName 字段指定另一个容器的名称。

9.4.3 使用 downwardAPI 卷将 Pod 元数据作为文件暴露

与配置映射(config maps)和密钥(secrets)一样,Pod 元数据也可以使用 downwardAPI 卷类型投射为容器文件系统中的文件。假设你希望在容器内的 /pod-metadata/podname 文件中暴露 Pod 的名称。以下清单展示了需要添加到 Pod 的卷和卷挂载定义。

清单 9.21 将 Pod 元数据注入到容器的文件系统中

列表中的 Pod 清单包含一个类型为 downwardAPI 的卷。卷定义包含一个名为 podname.txt 的单个文件,该文件包含从 Pod 对象的 metadata.name 字段读取的 Pod 名称。该卷挂载在容器的文件系统中,路径为 /pod-metadata。与环境变量类似,downwardAPI 卷定义中的每一项使用 fieldRef 引用 Pod 对象的字段,或使用 resourceFieldRef 引用容器的资源字段。对于资源字段,必须指定 containerName 字段,因为卷是在 Pod 级别定义的,而且并不明确是引用哪个容器的资源。与环境变量一样,可以指定一个除数将值转换为期望的单位。与 configMap 和 secret 卷类似,你可以使用 defaultMode 字段设置默认文件权限,或者使用 mode 字段为每个文件单独设置权限,如前面所述。

9.5使用投影卷将多个卷合并为一个卷

在本章中,您学习了如何使用三种特殊的卷类型,从配置映射、密钥以及 Pod 对象本身中注入值。除非在您的 volumeMount 定义中使用 subPath 字段,否则您无法将这些不同来源的文件,甚至同一类型的多个来源的文件,注入到同一个文件目录中。例如,您无法将来自不同密钥的键组合到一个卷中,然后将它们挂载到同一个文件目录中。虽然 subPath 字段允许您从多个卷中注入单个文件,但它并不是最终解决方案,因为当源值发生变化时,它会阻止文件被更新。如果您需要从多个来源填充单个卷,可以使用投影卷类型。

9.5.1 引入投影卷类型

投影卷允许您将来自多个配置映射、秘密和向下API的信息组合到单个pod卷中,然后可以将其挂载到pod的容器中。它们的行为与您在本章前几节中学习的configMap、secret和downwardAPI卷完全相同。它们提供与其他卷类型相同的特性和配置方式。

下图显示了投影卷的可视化。

图9.8使用带有多个源的投影卷

9.5.2 在 Pod 中使用投影卷

在本章的最后一个练习中,你将修改 kiada-ssl Pod,使 envoy 容器使用单个投影卷。之前版本的 Pod 使用了一个挂载在 /etc/envoy 的 configMap 卷来注入 envoy.yaml 配置文件,以及一个挂载在 /etc/certs 的 secret 卷来注入 TLS 证书和密钥文件。现在,你将用一个单独的投影卷替换这两个卷。这将允许你将所有三个文件保存在同一个目录(/etc/envoy)中。首先,你需要更改 kiada-envoy-config configMap 中 envoy.yaml 配置文件的 TLS 证书路径,使证书和密钥从同一个目录读取。编辑后,configMap 中的行应如下所示:

你可以在文件 pod.kiada-ssl.projected-volume.yaml 中找到带有投影卷的 Pod 清单。相关部分显示在下一个列表中。

列表 9.22 使用投影卷而不是 configMap 和 secret 卷。

该清单显示在 Pod 中定义了一个名为 etc-envoy 的单一投影卷。该卷使用了两个来源。第一个是 kiada-envoy-config 配置映射。该配置映射中的所有条目都会成为投影卷中的文件。第二个来源是 kiada-tls 密钥。其中的两个条目会成为投影卷中的文件——tls.crt 键的值会成为 example-com.crt 文件,而 tls.key 键的值会成为卷中的 example-com.key 文件。该卷以只读模式挂载在 envoy 容器的 /etc/envoy 路径下。正如你所看到的,投影卷中的源定义与前面章节中创建的 configMap 和 secret 卷没有太大区别。因此,对投影卷不需要进一步解释。你在其他卷中学到的所有知识同样适用于这种新卷类型。

9.6 总结

本章关于如何将配置数据传递给容器就到此为止。你已经了解到:

  • 容器镜像中定义的默认命令和参数可以在 Pod 清单中被覆盖。
  • 每个容器的环境变量也可以在 Pod 清单中设置。它们的值可以在清单中硬编码,也可以来自其他 Kubernetes API 对象。
  • 配置映射是 Kubernetes API 对象,用于以键/值对的形式存储配置信息。Secrets(密钥)是另一种类似的对象类型,用于存储敏感数据,如凭证、证书和身份验证密钥。
  • 配置映射和密钥的条目可以通过 configMap 和 secret 卷在容器中以环境变量或文件的形式暴露。
  • 可以使用 kubectl edit 命令直接编辑配置映射和其他 API 对象。
  • 向下 API(Downward API)提供了一种将 Pod 元数据暴露给在其中运行的应用程序的方法。与配置映射(ConfigMap)和密钥(Secrets)类似,这些数据可以注入到环境变量或文件中。
  • 投影卷(Projected Volumes)可以用于将可能类型不同的多个卷组合成一个复合卷,并挂载到单一目录中,而不是被迫将每个单独的卷挂载到各自的目录

到目前为止,你已经看到在 Kubernetes 中部署的应用程序可能需要许多额外的对象。如果你在同一集群中部署许多应用程序,你需要对它们进行组织,使每个人都能清楚每个对象放置的位置。在下一章,你将学习如何做到这一点。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐