Kubernetes Secrets 和 ConfigMaps 简介

Kubernetes Secrets 和 ConfigMaps 将各个容器实例的配置与容器镜像分离,从而减少了开销并增加了灵活性。
255 位读者喜欢这个。
Kubernetes

Jason Baker。CC BY-SA 4.0。

Kubernetes 有两种类型的对象可以在容器启动时将配置数据注入到容器中:Secrets 和 ConfigMaps。Secrets 和 ConfigMaps 在 Kubernetes 中的行为方式类似,无论是它们的创建方式,还是它们都可以作为挂载的文件或卷或环境变量在容器内部公开。

为了探索 Secrets 和 ConfigMaps,请考虑以下场景

您正在 Kubernetes 中运行 官方 MariaDB 容器镜像,并且必须进行一些配置才能使容器运行。该镜像需要设置环境变量 MYSQL_ROOT_PASSWORDMYSQL_ALLOW_EMPTY_PASSWORDMYSQL_RANDOM_ROOT_PASSWORD 以初始化数据库。它还允许通过将自定义配置文件放置在 /etc/mysql/conf.d 中来扩展 MySQL 配置文件 my.cnf

您可以构建一个自定义镜像,在其中设置环境变量并将配置文件复制到其中以创建定制的容器镜像。但是,最佳实践是创建和使用通用镜像,并向从中创建的容器添加配置。这是 ConfigMaps 和 Secrets 的完美用例。MYSQL_ROOT_PASSWORD 可以在 Secret 中设置,并作为环境变量添加到容器中,而配置文件可以存储在 ConfigMap 中,并在启动时作为文件挂载到容器中。

让我们试一下!

但首先:关于 Kubectl 的快速说明

请确保您的 kubectl 客户端命令版本与正在使用的 Kubernetes 集群版本相同或更新。

类似如下的错误: 

error: SchemaError(io.k8s.api.admissionregistration.v1beta1.ServiceReference): invalid object doesn't have additional properties

可能意味着客户端版本太旧,需要升级。   Kubernetes 安装 Kubectl 文档 提供了在各种平台上安装最新客户端的说明。

如果您使用的是 Docker for Mac,它也会安装自己的 kubectl 版本,这可能是问题所在。您可以使用 brew install 安装当前客户端,替换 Docker 附带的客户端的符号链接

$ rm /usr/local/bin/kubectl
$ brew link --overwrite kubernetes-cli

较新的 kubectl 客户端应继续与 Docker 的 Kubernetes 版本一起工作。

Secrets

Secrets 是 Kubernetes 对象,旨在存储少量敏感数据。值得注意的是,Secrets 在 Kubernetes 中以 base64 编码存储,因此它们并非完全安全。请确保具有适当的基于角色的访问控制 (RBAC) 来保护对 Secrets 的访问。即便如此,极其敏感的 Secrets 数据可能应该使用类似 HashiCorp Vault 的东西来存储。但是,对于 MariaDB 数据库的 root 密码,base64 编码就足够了。

手动创建 Secret

要创建包含 MYSQL_ROOT_PASSWORD 的 Secret,请选择一个密码并将其转换为 base64

# The root password will be "KubernetesRocks!"
$ echo -n 'KubernetesRocks!' | base64
S3ViZXJuZXRlc1JvY2tzIQ==

记下编码后的字符串。您需要它来创建 Secret 的 YAML 文件

apiVersion: v1
kind: Secret
metadata:
  name: mariadb-root-password
type: Opaque
data:
  password: S3ViZXJuZXRlc1JvY2tzIQ==

将该文件另存为 mysql-secret.yaml,并使用 kubectl apply 命令在 Kubernetes 中创建 Secret

$ kubectl apply -f mysql-secret.yaml
secret/mariadb-root-password created

查看新创建的 Secret

现在您已经创建了 Secret,请使用 kubectl describe 来查看它

$ kubectl describe secret mariadb-root-password
Name:         mariadb-root-password
Namespace:    secrets-and-configmaps
Labels:       <none>
Annotations:
Type:         Opaque

Data
====
password:  16 bytes

请注意,Data 字段包含您在 YAML 中设置的键:password。分配给该键的值是您创建的密码,但它不会显示在输出中。相反,值的大小会显示在其位置,在本例中为 16 字节。

您还可以使用 kubectl edit secret <secretname> 命令来查看和编辑 Secret。如果您编辑 Secret,您将看到类似这样的内容

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
  password: S3ViZXJuZXRlc1JvY2tzIQ==
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"password":"S3ViZXJuZXRlc1JvY2tzIQ=="},"kind":"Secret","metadata":{"annotations":{},"name":"mariadb-root-password","namespace":"secrets-and-configmaps"},"type":"Opaque"}
  creationTimestamp: 2019-05-29T12:06:09Z
  name: mariadb-root-password
  namespace: secrets-and-configmaps
  resourceVersion: "85154772"
  selfLink: /api/v1/namespaces/secrets-and-configmaps/secrets/mariadb-root-password
  uid: 2542dadb-820a-11e9-ae24-005056a1db05
type: Opaque

同样,带有 password 键的 data 字段是可见的,这次您可以看到 base64 编码的 Secret。

解码 Secret

假设您需要以纯文本形式查看 Secret,例如,验证 Secret 是否使用正确的内容创建。您可以通过解码来做到这一点。

通过提取值并将其管道传输到 base64,可以轻松解码 Secret。在本例中,您将使用输出格式 -o jsonpath=<path> 仅使用 JSONPath 模板提取 Secret 值。

# Returns the base64 encoded secret string
$ kubectl get secret mariadb-root-password -o jsonpath='{.data.password}'
S3ViZXJuZXRlc1JvY2tzIQ==

# Pipe it to `base64 --decode -` to decode:
$ kubectl get secret mariadb-root-password -o jsonpath='{.data.password}' | base64 --decode -
KubernetesRocks!

创建 Secrets 的另一种方法

您还可以直接使用 kubectl create secret 命令创建 Secrets。MariaDB 镜像允许通过设置 MYSQL_USERMYSQL_PASSWORD 环境变量来设置常规数据库用户及其密码。一个 Secret 可以容纳多个键/值对,因此您可以创建一个 Secret 来同时保存这两个字符串。作为奖励,通过使用 kubectl create secret,您可以让 Kubernetes 处理 base64,这样您就不必自己处理了。

$ kubectl create secret generic mariadb-user-creds \
      --from-literal=MYSQL_USER=kubeuser\
      --from-literal=MYSQL_PASSWORD=kube-still-rocks
secret/mariadb-user-creds created

请注意 --from-literal,它在一个命令中设置了键名和值。您可以根据需要传递任意数量的 --from-literal 参数,以在 Secret 中创建一个或多个键/值对。

使用 kubectl get secrets 命令验证用户名和密码是否已正确创建和存储

# Get the username
$ kubectl get secret mariadb-user-creds -o jsonpath='{.data.MYSQL_USER}' | base64 --decode -
kubeuser

# Get the password
$ kubectl get secret mariadb-user-creds -o jsonpath='{.data.MYSQL_PASSWORD}' | base64 --decode -
kube-still-rocks

ConfigMaps

ConfigMaps 类似于 Secrets。它们可以以相同的方式创建并在容器中共享。它们之间唯一的重大区别是 base64 编码混淆。ConfigMaps 旨在用于非敏感数据(配置数据),例如配置文件和环境变量,并且是从通用容器镜像创建自定义运行服务的绝佳方式。

创建 ConfigMap

ConfigMaps 可以以与 Secrets 相同的方式创建。您可以手动编写 ConfigMap 的 YAML 表示形式并将其加载到 Kubernetes 中,或者您可以使用 kubectl create configmap 命令从命令行创建它。以下示例使用后一种方法创建 ConfigMap,但不是传递文字字符串(如上面的 Secret 中的 --from-literal=<key>=<string>),而是从现有文件(旨在用于容器中 /etc/mysql/conf.d 的 MySQL 配置)创建 ConfigMap。此配置文件覆盖 MariaDB 默认设置为 16M 的 max_allowed_packet 设置。

首先,创建一个名为 max_allowed_packet.cnf 的文件,内容如下

[mysqld]
max_allowed_packet = 64M

这将覆盖 my.cnf 文件中的默认设置,并将 max_allowed_packet 设置为 64M。

创建文件后,您可以使用包含该文件的 kubectl create configmap 命令创建一个名为 mariadb-config 的 ConfigMap

$ kubectl create configmap mariadb-config --from-file=max_allowed_packet.cnf
configmap/mariadb-config created

与 Secrets 一样,ConfigMaps 在其 Data 哈希的对象中存储一个或多个键/值对。默认情况下,使用 --from-file=<filename>(如上例所示)会将文件的内容存储为值,并将文件名存储为键。从组织的角度来看,这很方便。但是,也可以显式设置键名。例如,如果您在创建 ConfigMap 时使用 --from-file=max-packet=max_allowed_packet.cnf,则键将是 max-packet 而不是文件名。如果要在 ConfigMap 中存储多个文件,您可以为每个文件添加一个额外的 --from-file=<filename> 参数。

查看新的 ConfigMap 并读取数据

如前所述,ConfigMaps 并非旨在存储敏感数据,因此在创建 ConfigMap 时不会对数据进行编码。这使得查看和验证数据以及直接编辑数据变得容易。

首先,验证 ConfigMap 是否已实际创建

$ kubectl get configmap mariadb-config
NAME             DATA      AGE
mariadb-config   1         9m

可以使用 kubectl describe 命令查看 ConfigMap 的内容。请注意,文件的完整内容是可见的,并且键名实际上是文件名 max_allowed_packet.cnf

$ kubectl describe cm mariadb-config
Name:         mariadb-config
Namespace:    secrets-and-configmaps
Labels:       <none>
Annotations:  <none>


Data
====
max_allowed_packet.cnf:
----
[mysqld]
max_allowed_packet = 64M

Events:  <none>

可以使用 kubectl edit 命令在 Kubernetes 中实时编辑 ConfigMap。这样做将打开一个带有默认编辑器的缓冲区,显示 ConfigMap 的 YAML 内容。保存更改后,它们将立即在 Kubernetes 中生效。虽然不是真正的最佳实践,但在开发中测试某些东西时可能会很方便。

假设您想要 max_allowed_packet 的值为 32M,而不是默认的 16M 或 max_allowed_packet.cnf 文件中的 64M。使用 kubectl edit configmap mariadb-config 编辑该值

$ kubectl edit configmap mariadb-config

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1

data:
  max_allowed_packet.cnf: |
    [mysqld]
    max_allowed_packet = 32M
kind: ConfigMap
metadata:
  creationTimestamp: 2019-05-30T12:02:22Z
  name: mariadb-config
  namespace: secrets-and-configmaps
  resourceVersion: "85609912"
  selfLink: /api/v1/namespaces/secrets-and-configmaps/configmaps/mariadb-config
  uid: c83ccfae-82d2-11e9-832f-005056a1102f

保存更改后,验证数据是否已更新

# Note the '.' in max_allowed_packet.cnf needs to be escaped
$ kubectl get configmap mariadb-config -o "jsonpath={.data['max_allowed_packet\.cnf']}"

[mysqld]
max_allowed_packet = 32M

使用 Secrets 和 ConfigMaps

Secrets 和 ConfigMaps 可以作为环境变量或文件挂载在容器中。对于 MariaDB 容器,您需要将 Secrets 作为环境变量挂载,并将 ConfigMap 作为文件挂载。但是,首先,您需要为 MariaDB 编写一个 Deployment,以便您有一些可以使用的东西。创建一个名为 mariadb-deployment.yaml 的文件,内容如下

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mariadb
  name: mariadb-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mariadb
  template:
    metadata:
      labels:
        app: mariadb
    spec:
      containers:
      - name: mariadb
        image: docker.io/mariadb:10.4
        ports:
        - containerPort: 3306
          protocol: TCP
        volumeMounts:
        - mountPath: /var/lib/mysql
          name: mariadb-volume-1
      volumes:
      - emptyDir: {}
        name: mariadb-volume-1

这是来自 Docker Hub 的官方 MariaDB 10.4 镜像的精简 Kubernetes Deployment。现在,添加您的 Secrets 和 ConfigMap。

将 Secrets 作为环境变量添加到 Deployment

您有两个 Secrets 需要添加到 Deployment

  1. mariadb-root-password(带有一个键/值对)
  2. mariadb-user-creds(带有两个键/值对)

对于 mariadb-root-password Secret,通过在 Deployment 中的容器规范中添加 env 列表/数组,并将环境变量值设置为 Secret 中键的值,来指定 Secret 和您想要的键。在本例中,该列表仅包含一个条目,用于变量 MYSQL_ROOT_PASSWORD

 env:
   - name: MYSQL_ROOT_PASSWORD
     valueFrom:
       secretKeyRef:
         name: mariadb-root-password
         key: password

请注意,对象的名称是添加到容器的环境变量的名称。valueFrom 字段将 secretKeyRef 定义为将从中设置环境变量的来源;即,它将使用您之前设置的 mariadb-root-password Secret 中 password 键的值。

将此部分添加到 mariadb-deployment.yaml 文件中 mariadb 容器的定义中。它应该看起来像这样

 spec:
   containers:
   - name: mariadb
     image: docker.io/mariadb:10.4
     env:
       - name: MYSQL_ROOT_PASSWORD
         valueFrom:
           secretKeyRef:
             name: mariadb-root-password
             key: password
     ports:
     - containerPort: 3306
       protocol: TCP
     volumeMounts:
     - mountPath: /var/lib/mysql
       name: mariadb-volume-1

通过这种方式,您已显式将变量设置为 Secret 中特定键的值。此方法也可以与 ConfigMaps 一起使用,方法是使用 configMapRef 而不是 secretKeyRef

您还可以从 Secret 或 ConfigMap 中的所有键/值对设置环境变量,以自动使用键名作为环境变量名,键的值作为环境变量的值。通过在容器规范中使用 envFrom 而不是 env,您可以一次性从您之前创建的 mariadb-user-creds Secret 设置 MYSQL_USERMYSQL_PASSWORD

envFrom:
- secretRef:
    name: mariadb-user-creds

envFrom 是 Kubernetes 获取环境变量的源列表。再次使用 secretRef,这次将 mariadb-user-creds 指定为环境变量的来源。就这样!Secret 中的所有键和值都将作为环境变量添加到容器中。

容器规范现在应如下所示

spec:
  containers:
  - name: mariadb
    image: docker.io/mariadb:10.4
    env:
      - name: MYSQL_ROOT_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mariadb-root-password
            key: password
    envFrom:
    - secretRef:
        name: mariadb-user-creds
    ports:
    - containerPort: 3306
      protocol: TCP
    volumeMounts:
    - mountPath: /var/lib/mysql
      name: mariadb-volume-1

注意: 您也可以将 mysql-root-password Secret 添加到 envFrom 列表并让它也被解析,只要 password 键被命名为 MYSQL_ROOT_PASSWORD 即可。使用 envFrom 没有办法像 env 那样手动指定环境变量名称。

将 max_allowed_packet.cnf 文件作为 volumeMount 添加到 Deployment

如前所述,envenvFrom 都可以用于与容器共享 ConfigMap 键/值对。但是,在 mariadb-config ConfigMap 的情况下,您的整个文件都存储为键的值,并且该文件需要存在于容器的文件系统中,MariaDB 才能使用它。幸运的是,Secrets 和 ConfigMaps 都可以作为 Kubernetes “卷”的来源,并挂载到容器中,而不是使用文件系统或块设备作为要挂载的卷。

mariadb-deployment.yaml 已经指定了一个卷和 volumeMount,一个 emptyDir(实际上是临时或短暂的)卷挂载到 /var/lib/mysql 以存储 MariaDB 数据

<...>

  volumeMounts:
  - mountPath: /var/lib/mysql
    name: mariadb-volume-1

<...>

volumes:
- emptyDir: {}
name: mariadb-volume-1

<...>

注意: 这不是生产配置。当 Pod 重新启动时,emptyDir 卷中的数据将丢失。这主要用于开发,或者当卷的内容不需要持久化时。

您可以通过将其添加到卷列表,然后为其容器定义添加 volumeMount 来添加您的 ConfigMap 作为源

<...>

  volumeMounts:
  - mountPath: /var/lib/mysql
    name: mariadb-volume-1
  - mountPath: /etc/mysql/conf.d
    name: mariadb-config

<...>

volumes:
- emptyDir: {}
  name: mariadb-volume-1
- configMap:
    name: mariadb-config
    items:
      - key: max_allowed_packet.cnf
        path: max_allowed_packet.cnf
  name: mariadb-config-volume

<...>

volumeMount 非常不言自明 - 为 mariadb-config-volume(在下面的 volumes 列表中指定)创建一个卷挂载到路径 /etc/mysql/conf.d

然后,在 volumes 列表中,configMap 告诉 Kubernetes 使用 mariadb-config ConfigMap,获取键 max_allowed_packet.cnf 的内容并将其挂载到路径 max_allowed_packed.cnf。卷的名称是 mariadb-config-volume,它在上面的 volumeMounts 中被引用。

注意: 来自 configMappath 是一个文件名,该文件将包含键的值的内容。在本例中,您的键也是文件名,但它不必是。另请注意,items 是一个列表,因此可以引用多个键,并且它们的值可以作为文件挂载。这些文件都将在上面指定的 volumeMountmountPath 中创建:/etc/mysql/conf.d

从 Deployment 创建 MariaDB 实例

此时,您应该有足够的条件来创建一个 MariaDB 实例。您有两个 Secrets,一个保存 MYSQL_ROOT_PASSWORD,另一个存储 MYSQL_USERMYSQL_PASSWORD 环境变量以添加到容器中。您还有一个 ConfigMap,其中包含一个 MySQL 配置文件的内容,该文件覆盖了 max_allowed_packed 值的默认设置。

你还有一个 mariadb-deployment.yaml 文件,该文件描述了带有 MariaDB 容器的 Pod 的 Kubernetes 部署,并将 Secrets 作为环境变量添加到容器中,并将 ConfigMap 作为卷挂载文件添加到容器中。它应该看起来像这样

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mariadb
  name: mariadb-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mariadb
  template:
    metadata:
      labels:
        app: mariadb
    spec:
      containers:
      - image: docker.io/mariadb:10.4
        name: mariadb
        env:
          - name: MYSQL_ROOT_PASSWORD
            valueFrom:
              secretKeyRef:
                name: mariadb-root-password
                key: password
        envFrom:
        - secretRef:
            name: mariadb-user-creds
        ports:
        - containerPort: 3306
          protocol: TCP
        volumeMounts:
        - mountPath: /var/lib/mysql
          name: mariadb-volume-1
        - mountPath: /etc/mysql/conf.d
          name: mariadb-config-volume
      volumes:
      - emptyDir: {}
        name: mariadb-volume-1
      - configMap:
          name: mariadb-config
          items:
            - key: max_allowed_packet.cnf
              path: max_allowed_packet.cnf
        name: mariadb-config-volume

创建 MariaDB 实例

使用 kubectl create 命令从 YAML 文件创建一个新的 MariaDB 实例

$ kubectl create -f mariadb-deployment.yaml
deployment.apps/mariadb-deployment created

部署创建完成后,使用 kubectl get 命令查看正在运行的 MariaDB Pod

$ kubectl get pods
NAME                                  READY     STATUS    RESTARTS   AGE
mariadb-deployment-5465c6655c-7jfqm   1/1       Running   0          3m

记下 Pod 名称(在本例中,它是 mariadb-deployment-5465c6655c-7jfqm)。请注意,Pod 名称将与此示例不同。

验证实例正在使用 Secrets 和 ConfigMap

使用 kubectl exec 命令(使用您的 Pod 名称)来验证 Secrets 和 ConfigMaps 正在使用中。例如,检查环境变量是否在容器中公开。

$ kubectl exec -it mariadb-deployment-5465c6655c-7jfqm env |grep MYSQL
MYSQL_PASSWORD=kube-still-rocks
MYSQL_USER=kubeuser
MYSQL_ROOT_PASSWORD=KubernetesRocks!

成功!所有三个环境变量——一个使用 env 设置来指定 Secret,另外两个使用 envFrom 来挂载 Secret 中的所有值——都可以在容器中供 MariaDB 使用。

抽查 max_allowed_packet.cnf 文件是否在 /etc/mysql/conf.d 中创建,并且它是否包含预期内容

$ kubectl exec -it mariadb-deployment-5465c6655c-7jfqm ls /etc/mysql/conf.d
max_allowed_packet.cnf

$ kubectl exec -it mariadb-deployment-5465c6655c-7jfqm cat /etc/mysql/conf.d/max_allowed_packet.cnf
[mysqld]
max_allowed_packet = 32M

最后,验证 MariaDB 是否使用环境变量设置了 root 用户密码,并读取了 max_allowed_packet.cnf 文件来设置 max_allowed_packet 配置变量。再次使用 kubectl exec 命令,这次是在运行的容器内获取 shell,并使用它来运行一些 mysql 命令

$ kubectl exec -it mariadb-deployment-5465c6655c-7jfqm /
bin/sh

# Check that the root password was set correctly
$ mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e 'show databases;'
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+

# Check that the max_allowed_packet.cnf was parsed
$ mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e "SHOW VARIABLES LIKE 'max_allowed_packet';"
+--------------------+----------+
| Variable_name      | Value    |
+--------------------+----------+
| max_allowed_packet | 33554432 |
+--------------------+----------+

Secrets 和 ConfigMaps 的优势

本练习解释了如何创建 Kubernetes Secrets 和 ConfigMaps,以及如何通过将这些 Secrets 和 ConfigMaps 作为环境变量或文件添加到正在运行的容器实例内部来使用它们。这使得将容器各个实例的配置与容器镜像分开变得容易。通过分离配置数据,开销降低到仅维护特定类型的实例的单个镜像,同时保留了创建具有各种配置的实例的灵活性。

标签
Chris Collins
Chris Collins 是 Red Hat 的一位 SRE,也是 OpenSource.com 的通讯员,他对自动化、容器编排及其周围的生态系统充满热情,并喜欢在家中为了乐趣而重现企业级技术。

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.