Kubernetes 为常规数据和机密数据提供了原生的配置资源,从而实现了配置生命周期与应用生命周期的解耦。配置资源模式解释了 ConfigMap 和 Secret 资源的概念,以及我们如何使用它们,以及它们的限制。
问题描述
环境变量配置模式有一个很大的缺点,就是它只适合少数变量和简单配置。另一个是由于环境变量可以在不同的地方定义,所以往往很难找到一个变量的定义。而且即使找到了,你也不能完全确定它没有在其他位置被覆盖。例如,在 Docker 镜像中定义的环境变量可以在 Kubernetes 部署资源中的运行时被替换。
通常情况下,最好将所有的配置数据保存在一个地方,而不是分散在各种资源定义文件中。但把整个配置文件的内容放到一个环境变量中是没有意义的。所以,一些额外的间接性将允许更多的灵活性,这正是 Kubernetes 配置资源所提供的。
解决方案
Kubernetes 提供了专门的配置资源,比单纯的环境变量更加灵活。它们分别是 ConfigMap 和 Secre t对象,分别用于通用数据和敏感数据。
:::tips 我们可以以同样的方式使用这两个对象,因为两者都提供了键值对的存储和管理。当我们在描述 ConfigMap 时,同样可以应用大部分的时间也是保密的。除了实际的数据编码(对 Secret 来说是 Base64),ConfigMap 和 Secret 的使用在技术上没有区别。 :::
一旦创建了 ConfigMap 并保存了数据,我们可以在两种情况下使用 ConfigMap 的键:
- 作为环境变量的引用其中键是环境变量的名称。
- 作为文件,映射到安装在 Pod 中的卷。键被用作文件名。
:::tips 当通过 Kubernetes API 更新 ConfigMap 时,挂载的 ConfigMap 卷中的文件会被更新。因此,如果一个应用程序支持配置文件的热重载,它可以立即从这样的更新中受益。但是,如果将 ConfigMap 条目作为环境变量使用,则不会反映更新,因为环境变量在进程启动后无法更改。 :::
除了 ConfigMap 和 Secret,另一种选择是直接将配置存储在外部卷中,然后挂载。下面的例子集中在 ConfigMap 的使用上,但也可以用于 Secret。但有一个很大的区别:Secret 的值必须是 Base64 编码。
ConfigMap 资源在其数据部分包含键值对,如例 19-1 所示。
# 例 19-1 ConfigMap 资源
---
apiVersion: v1
kind: ConfigMap
metadata:
name: random-generator-config
data:
# ConfigMap 可以作为环境变量和挂载文件访问
# 我们建议在 ConfigMap 中使用大写键来表示环境变量的用法,而作为挂载文件使用时则使用文件名形式
PATTERN: Configuration Resource
application.properties: |
# Random Generator config
log.file=/tmp/generator.log
server.port=7070
EXTRA_OPTIONS: "high-secure,native"
SEED: "432576345"
我们在这里看到,ConfigMap 也可以携带完整的配置文件的内容,比如本例中的 Spring Boot application.properties
。你可以想象,对于一个非平凡的用例,这个部分可能会变得相当大!而不是手动创建完整的资源描述符,我们可以使用 kubectl
创建一个完整的配置文件。
与其手动创建完整的资源描述符,我们也可以使用 kubectl
来创建 ConfigMap 或 Secret。对于前面的例子,等效的 kubectl
命令看起来像例 19-2 中的那样。
# 例 19-2 从文件创建一个 ConfigMap
kubectl create cm spring-boot-config \
--from-literal=JAVA_OPTIONS=-Djava.security.egd=file:/dev/urandom \
--from-file=application.properties
然后,这个 ConfigMap 可以在不同的地方被读取 — 凡是定义了环境变量的地方,如例 19-3 所示:
# 例 19-3 从 ConfigMap 设置环境变量
---
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- env:
- name: PATTERN
valueFrom:
configMapKeyRef:
name: random-generator-config
key: PATTERN
....
如果一个 ConfigMap 有许多条目要作为环境变量使用,使用某种语法可以节省大量的输入。envFrom:
允许公开所有 ConfigMap 条目,这些条目的键也可以作为有效的环境变量使用,而不是像前面例子中的 env:
部分那样单独指定每个条目。我们可以在前面加上一个前缀,如例 19-4 所示:
# 例 19-4 从 ConfigMap 设置环境变量
---
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
# 从 ConfigMap random-generator-config 中提取所有可以使用的密钥,作为环境变量名
envFrom:
- configMapRef:
name: random-generator-config
# 在所有合适的 ConfigMap 键前加上 CONFIG_
# 对于例 19-1 中定义的 ConfigMap,这就导致了三个暴露的环境变量:
# CONFIG_PATTERN_NAME、CONFIG_EXTRA_OPTIONS 和 CONFIG_SEED
prefix: CONFIG_
与 ConfigMap 一样,Secret 也可以作为环境变量使用,可以是每个条目,也可以是所有条目。要访问 Secret 而不是 ConfigMap,请将 configMapKeyRef
替换为 secretKeyRef
。
当作为一个卷使用时,完整的 ConfigMap 被挂载到这个卷中,密钥被用作文件名。见例 19-5:
# 例 19-5 将 ConfigMap 挂载为卷
---
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
volumeMounts:
- name: config-volume
mountPath: /config
volumes:
- name: config-volume
# ConfigMap 支持的卷将包含与条目一样多的文件
# 其中 ConfigMap 的键作为文件名,值作为文件内容
configMap:
name: random-generator-config
例 19-1 中的配置是以卷的形式挂载的,结果在 /config
文件夹中出现了两个文件,一个是包含 ConfigMap 中定义的内容的application.properties
,另一个是只有一行内容的 PATTERN
文件。一个包含配置映射中定义的内容的 application.properties
,和一个只有一行内容的 PATTERN
文件。
:::tips 配置数据的映射可以通过在卷声明中添加附加属性进行更精细的调整。您也可以不将所有条目映射为文件,而是单独选择每一个应该被暴露的键以及在其下的文件名。更多详情请参考 ConfigMap 文档。 :::
另一种借助 Kubernetes 存储配置的方式是使用 gitRepo
卷。这种类型的卷在 Pod 上挂载一个空目录,并将一个 Git 仓库克隆到其中。在 Git 上保留配置的好处是,你可以免费获得版本管理和审计。但是 gitRepo
卷需要从外部访问 Git 仓库,Git 仓库不是 Kubernetes 资源,可能位于集群之外,需要单独监控和管理。克隆和挂载发生在 Pod 的启动过程中,本地克隆的仓库不会随着变化自动更新。这个卷的工作原理类似于第 20 章 “不可更改的配置” 中描述的方法,使用 Init 容器将配置复制到共享的本地卷中。
事实上,gitRepo
类型的卷现在已经被淘汰,转而使用基于 Init 容器的解决方案,因为这种方法更普遍适用,并且支持其他来源的数据,而不仅仅是 Git。所以今后,您可以使用同样的方法从外部系统中检索配置数据并将其存储到一个卷中,但不使用预定义的 gitRepo
卷,而是使用更灵活的Init 容器方法。我们在下一章的 “Kubernetes 初始化容器” 中详细解释了这种技术。
Secret 真的安全吗?
Secret 持有 Base64 编码的数据,并在将其作为环境变量或挂载卷传递给 Pod 之前对其进行解码。这经常被混淆为一种安全功能。Base64 编码不是一种加密方法,从安全角度来看,它被认为与纯文本相同。Secrets 中的 Base64 编码允许存储二进制数据,那么为什么 Secret 被认为比 ConfigMap 更安全呢?Secrets 的其他一些实现细节使其安全。在这一领域正在进行不断的改进,但主要的实现细节目前如下:
- 一个 Secret 只被分发到需要这个 Secret 的 Pod 的运行节点上
- 在节点上,Secret 被存储在一个 tmpfs 的内存中,从不写入物理存储中,当 Pod 被移除时,Secret 被移除
- 在 etcd 中,Secret 是以加密的形式存储的
:::danger 不管这些,仍然有办法以 root 用户的身份访问 Secret,甚至通过创建 Pod 和挂载 Secret。您可以将基于角色的访问控制(RBAC)应用于 Secret(就像您可以对 ConfigMap 或其他资源所做的那样),并且只允许具有预定义 ServiceAccount 的某些 Pod 读取它们。但是,有能力在命名空间中创建 Pod 的用户仍然可以通过创建 Pod 来升级他们在该命名空间中的权限。他们可以在权限更大的服务帐户下运行 Pod,并仍然可以读取 Secret。在命名空间中拥有创建 Pod 权限的用户或控制器可以冒充任何 ServiceAccount,访问该命名空间中的所有 Secret 和 ConfigMap。因此,敏感信息的额外加密通常也是在应用层进行的。 ::: Putting an Invisible Shield on Kubernetes Secrets_Kailun Qin_Ant Group.pdf
一些讨论
ConfigMap 和 Secret 允许将配置信息存储在专用的资源对象中,便于使用 Kubernetes API 进行管理。使用 ConfigMap 和 Secret 最显著的优势是它们将配置数据的定义与其使用解耦。这种解耦允许我们通过使用配置独立于配置来管理对象。
ConfigMap 和 Secret 的另一个好处是它们是平台的固有特性。不需要像第 20 章 “不可变的配置” 中那样的自定义构造。
:::tips 然而,这些配置资源也有其限制:Secret 的大小上限为 1 MB,它们不能存储任意大的数据,也不太适合用于非配置应用数据。你也可以在 Secret 中存储二进制数据,但由于它们必须是 Base64 编码,所以你只能使用 700 KB 左右的数据。 :::
现实世界的 Kubernetes 集群也会对每个命名空间或项目可以使用的 ConfigMap 的数量进行单独的配额,所以 ConfigMap 并不是一个金锤子。
接下来的两章将介绍如何通过使用 “不可变配置” 和 “配置模版” 来处理大量的配置数据。