那些我们很少关注的k8s Secret细节

43,866 阅读6分钟

写在开头:

最近因为业务上的需求涉及到k8s原生对象Secret的开发,之前有浅尝辄止地了解过一些Secret的知识点,但还没有真正开发过secret,所以此次把一些之前未注意到的小细节在这里给记了下来。


Secret简介

  • Secret 是一种包含少量敏感信息例如密码、令牌或密钥的对象。
  • Secret中保存的信息可以在Pod的Spec或者镜像中使用,且Secret的生命周期独立于使用它们的Pod。
  • 默认情况下,Kubernetes Secret 未加密地存储在 API 服务器的底层数据存储(etcd)中。 任何拥有 API 访问权限的人都可以检索或修改 Secret,任何有权访问 etcd 的人也可以。

data和stringData的区别

  • 在Secret中存储内容时,可以设置成data或者stringData字段,它们的区别是,data 字段中所有键值都必须是 base64 编码的字符串,而stringData则可以使用任何字符串。
  • data 和 stringData 中的键名只能包含字母、数字、-_ 或 . 字符,且stringData 字段中的所有键值对都会在内部被合并到 data 字段中。
  • 如果某个主键同时出现在 data 和 stringData 字段中,stringData 所指定的键值具有高优先级。

secret大小限制

  • 每个secret的大小最大为1MiB

Secret的使用方式

secret有三种使用方式,分别是:

  1. 作为挂载到一个或多个容器上的 中的文件
  2. 作为容器的环境变量
  3. 由 kubelet 在为 Pod 拉取镜像时使用
  • Kubernetes 会检查 Secret 的卷数据源,确保所指定的对象引用确实指向类型为 Secret 的对象。因此,如果 Pod 依赖于某 Secret,该 Secret 必须先于 Pod 被创建。
  • 如果 Secret 内容无法取回(可能因为 Secret 尚不存在或者临时性地出现 API 服务器网络连接问题),kubelet 会周期性地重试 Pod 运行操作。kubelet 也会为该 Pod 报告 Event 事件,给出读取 Secret 时遇到的问题细节。
  • 如果 Pod 引用了 Secret 中的特定主键,而虽然 Secret 本身存在,对应的主键不存在, Pod 启动也会失败。
  • 无法在静态pod中使用ConfigMap或者Secret。

方式1: 以文件的形式挂载到pod中

  • 当卷中包含来自 Secret 的数据,而对应的 Secret 被更新,Kubernetes 会跟踪到这一操作并更新卷中的数据。更新的方式是保证最终一致性。
  • 对于以 subPath 形式挂载 Secret 卷的容器而言, 它们无法收到自动的 Secret 更新。

将secret直接挂载进pod中

  1. 创建一个 Secret 或者使用已有的 Secret。多个 Pod 可以引用同一个 Secret。
  2. 更改 Pod 定义,在 .spec.volumes[] 下添加一个卷。根据需要为卷设置其名称, 并将 .spec.volumes[].secret.secretName 字段设置为 Secret 对象的名称。
  3. 为每个需要该 Secret 的容器添加 .spec.containers[].volumeMounts[]。 并将 .spec.containers[].volumeMounts[].readOnly 设置为 true, 将 .spec.containers[].volumeMounts[].mountPath 设置为希望 Secret 被放置的、目前尚未被使用的路径名。
  4. 更改你的镜像或命令行,以便程序读取所设置的目录下的文件。Secret 的 data 映射中的每个主键都成为 mountPath 下面的文件名。
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      optional: false # 默认设置,意味着 "mysecret" 必须已经存在

将secret的键投射到特定的目录中

mysecret 中的键 username 会出现在容器中的路径为 /etc/foo/my-group/my-username, 而不是 /etc/foo/username。Secret 对象的 password 键不会被投射。

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username
  • 如果使用了 .spec.volumes[].secret.items,则只有 items 中指定了的主键会被投射。 如果要使用 Secret 中的所有主键,则需要将它们全部枚举到 items 字段中。

  • 如果你显式地列举了主键,则所列举的主键都必须在对应的 Secret 中存在。 否则所在的卷不会被创建。

方式2: 以环境变量的方式使用Secret

  1. 创建 Secret(或者使用现有 Secret)。多个 Pod 可以引用同一个 Secret。
  2. 更改 Pod 定义,在要使用 Secret 键值的每个容器中添加与所使用的主键对应的环境变量。 读取 Secret 主键的环境变量应该在 env[].valueFrom.secretKeyRef 中填写 Secret 的名称和主键名称。
  3. 更改你的镜像或命令行,以便程序读取环境变量中保存的值。
apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
            optional: false # 此值为默认值;意味着 "mysecret"
                            # 必须存在且包含名为 "username" 的主键
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
            optional: false # 此值为默认值;意味着 "mysecret"
                            # 必须存在且包含名为 "password" 的主键
  restartPolicy: Never
  • 对于通过 envFrom 字段来填充环境变量的 Secret 而言, 如果其中包含的主键不能被当做合法的环境变量名,这些主键会被忽略掉。 Pod 仍然可以启动。
  • 如果容器已经在通过环境变量来使用 Secret,Secret 更新在容器内是看不到的, 除非容器被重启。

容器镜像拉取Secret

imagePullSecrets 字段是一个列表,包含对同一名字空间中 Secret 的引用。 你可以使用 imagePullSecrets 将包含 Docker(或其他)镜像仓库密码的 Secret 传递给 kubelet。kubelet 使用此信息来替 Pod 拉取私有镜像。

Secret的类型

secret的类型主要有以下这些:

内置类型用法
Opaque用户定义的任意数据
kubernetes.io/service-account-token服务账号令牌
kubernetes.io/dockercfg~/.dockercfg 文件的序列化形式
kubernetes.io/dockerconfigjson~/.docker/config.json 文件的序列化形式
kubernetes.io/basic-auth用于基本身份认证的凭据
kubernetes.io/ssh-auth用于 SSH 身份认证的凭据
kubernetes.io/tls用于 TLS 客户端或者服务器端的数据
bootstrap.kubernetes.io/token启动引导令牌数据

通过为 Secret 对象的 type 字段设置一个非空的字符串值,你也可以定义并使用自己 Secret 类型(如果 type 值为空字符串,则被视为 Opaque 类型)。

Docker配置Secret

apiVersion: v1
kind: Secret
metadata:
  name: secret-dockercfg
type: kubernetes.io/dockercfg
data:
  .dockercfg: |
        "<base64 encoded ~/.dockercfg file>"

basic-auth认证secret

apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: admin      # kubernetes.io/basic-auth 类型的必需字段
  password: t0p-Secret # kubernetes.io/basic-auth 类型的必需字段

ssh身份认证secret

apiVersion: v1
kind: Secret
metadata:
  name: secret-ssh-auth
type: kubernetes.io/ssh-auth
data:
  # 此例中的实际数据被截断
  ssh-privatekey: |
          MIIEpQIBAAKCAQEAulqb/Y ...

tls证书密钥secret

apiVersion: v1
kind: Secret
metadata:
  name: secret-tls
type: kubernetes.io/tls
data:
  # 此例中的数据被截断
  tls.crt: |
        MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
  tls.key: |
        MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...