跳到主要内容

13、Kubernetes - 实战:ConfigMap - Secret

ConfigMap

前面的资源对象并不能满足日常工作中的所有需求,一个最重要的需求就是应用的配置管理,特别是可变配置。

比如,在开发过程中程序需要配置 MySQL 或者 Redis 的连接地址。如果是以前的部署方式,此时想要修改这些信息,就需要修改代码的配置,然后重新打包部署。如果使用 ConfigMap,它能够向容器中注入配置信息,不仅可以是单个配置,也可以是整个配置文件。后面只需要修改 ConfigMap 的配置就能实现应用的配置更新。

ConfigMap 资源清单

ConfigMap 资源清单示例:

apiVersion: v1
kind: ConfigMap
metadata:
  name: cm-demo
data:
  serviceName: "demo-service"
  listenPort: "8080"
  config: |
    mysql.ip="192.168.2.1"
    mysql.port="3306"
    redis.ip="192.168.2.2"
    redis.port="6379"

在这个配置清单中,前两项是单个属性配置,config 字段中的可以看成是一个配置文件,其中的 | 的作用在于,保留下面属性的换行符和每行相对于第一行的缩进,多余的缩进和行尾的空白都会被删除。

使用示例:

config: |
  第一行
    第二行
      第三行
  第四行

转换成JSON 格式就是:

{"config": "第一行\n  第二行\n    第三行\n第四行"}

如果将| 换成 >,表示折叠的意思,只有空白和才会被识别成换行,原来的换行符则会被识别成空格:

config: >
  第一行
  第二行
  第三行

  第五行

转换成JSON 格式就是:

{"config": "第一行 第二行 第三行\n第五行"}

还可以使用竖线和加号或者减号进行配合使用,+ 表示保留文字块末尾的换行,- 表示删除字符串末尾的换行。

config: |
  "hello"
# Json 格式:{"config": "hello\n"}  
  
config: |-
  "hello"
# Json 格式:{"config": "hello"}  

config: |+
  "hello"
  
# Json 格式:{"config": "hello\n\n"}
# 有几个换行则加几个

创建 ConfigMap

创建ConfigMap 的命令:

# 通过指定目录创建 ConfigMap
kubectl create configmap cm-demo --from-file=/path/dir
  
# 通过指定文件创建 ConfigMap,可以是多个文件
kubectl create configmap cm-demo --from-file=key1=/path/dir/file1.txt --from-file=key2=/path/dir/file2.txt

# 直接指定键值创建 ConfigMap
kubectl create configmap cm-demo --from-literal=key1=value1 --from-literal=key2=value2

准备一个目录 /cm/config/,下面准备两个文件 mysql.confredis.conf

# mysql.conf 内容
host="192.168.2.1"
port="3306"

# redis.conf 内容
host="192.168.2.2"
port="6379"

测试创建:

# 直接通过整个目录创建,默认的 Key 就是文件名
kubectl create configmap cm-demo1 --from-file=/cm/config

# 指定文件创建,可以指定对于的 Key
kubectl create configmap cm-demo2 --from-file=mysqlConfig=/cm/config/mysql.conf --from-file=redisConfig=/cm/config/redis.conf

# 指定 K/V 直接创建
kubectl create configmap cm-demo3 --from-literal=k1=v1 --from-literal=k2=v2

使用 ConfigMap(环境变量)

在ConfigMap 创建成功之后,有以下方法在 Pod 中使用它:

  • 设置环境变量的值
  • 在容器中设置命令行参数
  • 在数据卷中挂载配置文件

使用环境变量示例:

apiVersion: v1
kind: Pod
metadata:
  name: pod-cm-demo1
spec:
  containers:
  - name: c-cm-demo1
    image: busybox:latest
    command: [/bin/sh, -c, "env"]
    将每个配置文件单独给一个环境变量
    env:
      - name: MYSQL_CONFIG
        valueFrom:
          configMapKeyRef:
            name: cm-demo1
            key: mysql.conf
      - name: REDIS_CONFIG
        valueFrom:
          configMapKeyRef:
            name: cm-demo1
            key: redis.conf
    直接导入整个 ConfigMap 到环境变量中
    envFrom:
      - configMapRef:
          name: cm-demo

通过查看创建 Pod 之后输出的日志,可以看到设置的环境变量为:

# cm-demo 的 mysql.conf Key
MYSQL_CONFIG=host="192.168.2.1"
port="3306"

# cm-demo 的 redis.conf Key
REDIS_CONFIG=host="192.168.2.2"
port="6379"

# cm-demo 的配置生成了三个环境变量
config=mysql.ip="192.168.2.1"
mysql.port="3306"
redis.ip="192.168.2.2"
redis.port="6379"

serviceName=demo-service
listenPort=8080

使用 ConfigMap(容器运行参数)

作为容器的运行参数示例:

apiVersion: v1
kind: Pod
metadata:
  name: pod-cm-demo2
spec:
  containers:
  - name: c-cm-demo2
    image: busybox:latest
    command: [/bin/sh, -c, "echo ${ENV_V1}-${ENV_V2}"]
    env:
      - name: ENV_V1
        valueFrom:
          configMapKeyRef:
            name: cm-demo3
            key: k1
      - name: ENV_V2
        valueFrom:
          configMapKeyRef:
            name: cm-demo3
            key: k2

使用 ConfigMap(数据卷挂载)

以数据卷的方式挂载到 Pod 中使用:

apiVersion: v1
kind: Pod
metadata:
  name: pod-cm-demo3
spec:
  volumes:
    - name: v-cm-demo
      configMap:
        name: cm-demo1
        items:
          - key: mysql.conf
            path: path/to/msyql.conf
  containers:
  - name: c-cm-demo3
    image: busybox:latest
    command: [/bin/sh, -c, "ls -lh /opt/config/ && cat /opt/config/mysql.conf"]
    volumeMounts:
      - name: v-cm-demo
        mountPath: /opt/config/

此时ConfigMap 中的 Key 就能够变成文件名,然后挂载到指定的目录下。当然,也可以自己指定挂载的文件:

...
      configMap:
        name: cm-demo1
        items:
          - key: mysql.conf
            path: /opt/config/mysql.conf
...

当使用数据卷的方式挂载到 Pod 中,此时更新 ConfigMap,挂载的数据也是会跟着热更新的。这意味着以配置文件名称作为 Key 名称,然后使用 volume 挂载的方式是最适合开发场景的。

只有通过 Kubernetes API 创建的 Pod 才能使用 ConfigMap,其他方式创建的(比如静态 Pod)不能使用,同时 ConfigMap 文件大小限制为 1MB(ETCD 的要求)。

Secret

ConfigMap 一般用于存储非安全的配置信息,原因在于 ConfigMap 使用的是明文的方式存储。这用来保存密码等对象显然是不合理的。此时就需要另外一个对象帮忙完成,那就是 Secret。

Secret 主要包含了以下几种类型:

1、 Opaque:base64编码的Secret,主要用来存储密码,密钥等但数据可以通过base64-d解码得到,加密性很弱;
2、 kubernetes.io/dockercfg~/dockercfg文件的序列化形式;
3、 kubernetes.io/dockerconfigjson:用来存储私有dockerregistry的认证信息,~/.docker/config.json文件的序列号形式;
4、 kubernetes.io/service-account-token:ServiceAccount在创建时,Kubernetes会默认创建一个对应的Secret对象,Pod如果使用ServiceAccount,对应的Secret会自动的挂载到Pod的/run/secret/kubernetes.io/serviceaccount中;
5、 kubernetes.io/ssh-auth:用于SSH身份认证的凭据;
6、 kubernetes.io/basic-auth:用于基本身份认证的凭据;
7、 bootstrap.kubernetes.io/token:用于节点接入集群校验的Secret;

上面是Secret 对象内置的几种类型,通过 Secret 的 type 字段设置,也可以定义自己的 Secret 类型。如果 type 字段为空,则使用默认的 Opaque 类型。

Opaque

Secret 资源包含 2 个键值对:

  • data:用于存储 base64 编码的任意数。
  • stringData:为了方便 secret 使用未编码的字符串。

比如,新建一个 username 为 admin,password 为 admin123 的 Secret 对象。

先对username 和 password 进行 base64 编码:

echo -n admin | base64
echo -n admin123 | base64

如图所示:

 

创建Secret 资源清单:

apiVersion: v1
kind: Secret
metadata:
  name: secret-demo
type: Opaque
data:
  username: YWRtaW4=
  password: YWRtaW4xMjM=

应用之后查看:

kubectl describe secrets secret-demo

如图所示:

 

可以看到 data 中的数据没有被显示出来,如果想要获取到数据可以以 yaml 当时显示:

kubectl get secrets secret-demo -o yaml

如图所示:

 

在某些时候,也可能不想手动编码,此时就可以将数据放到 stringData 中,那么在创建或者更新的 Secret 的时候就能自动对其进行编码,比如一个复杂点的场景:

apiVersion: v1
kind: Secret
metadata:
  name: secret-demo1
type: Opaque
stringData:
  mysql.yaml: |
    host: 192.168.2.1
    port: 3306
    username: root
    password: root123

此时看到整个数据的配置就是加密的状态:

 

解密查看:

 

创建好Secret 对象后,有两种方式来使用它:

  • 环境变量的方式
  • Volume 挂载的方式

使用 Secret(环境变量)

通过环境变量的方式使用 Secret:

apiVersion: v1
kind: Pod
metadata:
  name: pod-secret-demo1
spec:
  containers:
  - name: c-secret-demo
    image: busybox:latest
    command: ["/bin/sh", "-c", "env"]
    env:
      - name: USERNAME
        valueFrom:
          secretKeyRef:
            name: secret-demo
            key: username
      - name: PASSWORD
        valueFrom:
          secretKeyRef:
            name: secret-demo
            key: password

运行之后通过日志可以看到:

USERNAME=admin
PASSWORD=admin123

变量是以明文的方式注入到环境变量中的。

使用 Secret(数据卷挂载)

通过数据卷挂载的方式使用 Secret:

apiVersion: v1
kind: Pod
metadata:
  name: pod-secret-demo2
spec:
  volumes:
    - name: v-secret-demo
      secret:
        secretName: secret-demo1
  containers:
  - name: c-secret-demo2
    image: busybox:latest
    command: ["/bin/sh", "-c", "cat /opt/secret/mysql.yaml"]
    volumeMounts:
      - name: v-secret-demo
        mountPath: /opt/secret/

使用方法和 ConfigMap 是类似的,也可以通过 items 指定挂载到指定的文件。

kubernetes.io/dockerconfigjson

直接创建 docker registry 认证的 secret:

kubectl create secret docker-registry secret-docker-registry --docker-server="hub.docker.com" --docker-username="dylan" --docker-password="123456" --docker-email="1214966109@qq.com"

除此之外,也可以通过指定文件的方式创建镜像仓库认证信息:

kubectl create secret generic secret-docker-registry --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson

如图所示:

 

可以通过 base64 解码:

 

此时如果需要拉取私有仓库的镜像要用到认证的,就可以直接使用这个Secret:

apiVersion: v1
kind: Pod
metadata:
  name: pod-secret-demo2
spec:
  containers:
  - name: c-secret-demo2
    image: hub.docker.com/imageDemo:v1.0
  imagePullSecrets:
    - name: secret-docker-registry

特别注意:

ImagePullSecretsSecrets 不同,因为 Secrets 可以挂载到 Pod 中,但是 ImagePullSecrets 只能由 Kubelet 访问。

除了该方法设置 ImagePullSecrets 的方式访问私有仓库获取镜像以外,还可以通过 ServiceAccount 中设置 ImagePullSecrets 然后自动为使用了该 SA 的 Pod 注入配置信息。

kubernetes.io/basic-auth

该类型用来存放用于基本身份认证所需的凭据信息,使用这种 Secret 类型时,Secret 的 data(或 stringData)字段中一般会包含以下两个键:

  • username:用于身份认证的用户名。
  • password:用于身份认证的密码或令牌。

可以和创建 Opaque 一样通过 data 字段或者 stringData 字段定义。

apiVersion: v1
kind: Secret
metadata:
  name: secret-demo3
type: kubernetes.io/basic-auth
stringData:
  username: admin
  password: admin123

提供基本身份认证类型的 Secret 仅仅是出于用户方便性考虑,也可以使用 Opaque 类型来保存用于基本身份认证的凭据,不过使用内置的 Secret 类型的有助于对凭据格式进行统一处理。

kubernetes.io/ssh-auth

该类型用来存放 SSH 身份认证中所需要的凭据,使用这种 Secret 类型时,Secret 的 data(或 stringData)字段中一般会提供一个 ssh-privatekey 键值对,作为要使用的 SSH 凭据。

apiVersion: v1
kind: Secret
metadata:
  name: secret-demo4
type: kubernetes.io/ssh-auth
data:
  ssh-privatekey: |
          MIIEpQIBAAKCAQEAulqb...

同样提供 SSH 身份认证类型的 Secret 也仅仅是出于用户方便性考虑,也可以使用 Opaque 类型来保存用于 SSH 身份认证的凭据,只是使用内置的 Secret 类型的有助于对凭据格式进行统一处理。

kubernetes.io/tls

该类型用来存放证书及其相关密钥。该类型主要提供给 Ingress 资源,用以校验 TLS 链接,使用此类型的 Secret 时,Secret 的 data (或 stringData)字段必须包含 tls.keytls.crt 主键。

apiVersion: v1
kind: Secret
metadata:
  name: secret-demo4
type: kubernetes.io/tls
data:
  tls.crt: |
        MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
  tls.key: |
        MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...

提供TLS 类型的 Secret 仅仅是出于用户方便性考虑,也可以使用 Opaque 类型来保存用于 TLS 服务器与/或客户端的凭据。只是使用内置的 Secret 类型的有助于对凭据格式进行统一化处理。

当使用kubectl 来创建 TLS Secret 时,可以通过命令指定证书来直接创建更为方便:

kubectl create secret tls secret-demo4 --cert=/path/cert --key=/path/key

需要注意的是用于 --cert 的公钥证书必须是 .PEM 编码的 (Base64 编码的 DER 格式),且与 --key 所给定的私钥匹配,私钥必须是通常所说的 PEM 私钥格式,且未加密。

对这两个文件而言,PEM 格式数据的第一行和最后一行(--------BEGIN CERTIFICATE------------END CERTIFICATE----)都不会包含在其中。

kubernetes.io/service-account-token

ServiceAccount 是 Pod 和集群 apiserver 通讯的访问凭证。kubernetes.io/service-account-token 这种类型就是主要给 ServiceAccount 使用。ServiceAccount 在创建时会默认创建对应的 Secret。

查看任意一个 Pod:

kubectl get pod pod-secret-demo1 -o yaml

可以看到:

  • 当创建 Pod 的时候,如果没有指定 ServiceAccount,Pod 则会使用默认名称空间中名称为 default 的 ServiceAccount。
  • volumes 字段中有一个 projected 类型的 volume 被挂载到了 /var/run/secrets/kubernetes.io/serviceaccount,该类型的 volume 可以同时挂载多个来源的数据。默认挂载了 configMap,downwardAPI,serviceAccountToken。

更多信息由于需要等到学习了 ServiceAccount 的时候才能搞得清除。

补充说明

如果某个容器已经在通过环境变量使用某 Secret,对该 Secret 的更新不会被容器马上看见,除非容器被重启,当然也可以使用一些第三方的解决方案在 Secret 发生变化时触发容器重启。

在Kubernetes v1.21 版本提供了不可变的 Secret 和 ConfigMap 的可选配置 stable,对于大量使用 Secret 或 ConfigMap 的集群时,禁止变更具有以下好处:

  • 可以防止意外更新导致应用程序中断。
  • 通过将 Secret 标记为不可变来关闭 kube-apiserver 对其的 watch 操作,从而显著降低 kube-apiserver 的负载,提升集群性能。

配置方法:

apiVersion: v1
kind: Secret
metadata:
  ...
data:
  ...
immutable: true  标记为不可变

一旦Secret 或 ConfigMap 被标记为不可更改,撤销此操作或更改 data 字段的内容都是不允许的,只能删除并重新创建这个 Secret。现有的 Pod 将继续使用已删除 Secret 的挂载点,所以也需要重新引用的 Pod。

Secret vs ConfigMap

相同点:

  • k/v 形式
  • 属于特定的命名空间
  • 可以导出到环境变量
  • 可以通过目录/文件形式挂载
  • 通过 volume 挂载的配置信息均可热更新

不同点:

  • Secret 可以被 ServerAccount 关联。
  • Secret 可以存储 docker register 的鉴权信息,用在 ImagePullSecret 参数中,用于拉取私有仓库的镜像。
  • Secret 支持 Base64 加密。
  • Secret 有多种分类,而 ConfigMap 不区分类型。

同样 Secret 文件大小限制为 1MB(ETCD 的要求),Secret 虽然采用 Base64 编码,但还是可以很方便解码获取到原始信息,所以对于非常重要的数据还是需要慎重考虑,可以考虑使用 Vault 来进行加密管理。