k8s secret对象详解

  • 1. Secret 介绍-分为三大类
    • 1.1 Opaque Secret方式
      • 1.1.1 通过volume挂载和环境变量的区别
      • 1.1.2 Secret 与 ConfigMap 对比
    • 1.2 kubernetes.io/dockerconfigjson
    • 1.3 Service Account类型
    • 1.4 secret三种类型的原理
  • 3.附录
    • 3.1 K8S Configmap 和 Secret 作为 Volume 的热更新原理
      • 热更新原理
      • 参考文献

1. Secret 介绍-分为三大类

Secret解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者Pod Spec中。Secret可以以Volume或者环境变量的方式使用。

Secret有三种类型:

  • Service Account:用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中;
  • Opaque:base64编码格式的Secret,用来存储密码、密钥等;
  • kubernetes.io/dockerconfigjson:用来存储私有docker registry的认证信息。

具体详见结构体定义:type Secret struct

1.1 Opaque Secret方式

Opaque类型的数据是一个map类型,要求value是base64编码格式:

$ echo -n "admin" | base64
YWRtaW4=
$ echo -n "1f2d1e2e67df" | base64
MWYyZDFlMmU2N2Rm

secrets.yml

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  password: MWYyZDFlMmU2N2Rm
  username: YWRtaW4=

接着,就可以创建secret了:kubectl create -f secrets.yml

创建好secret之后,有两种方式来使用它:

  • 以Volume方式
  • 以环境变量方式

(1)volume方式

#test-projected-volume.yaml
 
apiVersion: v1
kind: Pod
metadata:
  name: test-projected-volume 
spec:
  containers:
  - name: test-secret-volume
    image: busybox
    args:
    - sleep
    - "86400"
    volumeMounts:
    - name: mysql-cred
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: mysql-cred
    projected:
      sources:
      - secret:
          name: user
      - secret:
          name: pass

当 Pod 变成 Running 状态之后,我们再验证一下这些 Secret 对象是不是已经在容器里了:

$ kubectl exec -it test-projected-volume -- /bin/sh
$ ls /projected-volume/
user
pass
$ cat /projected-volume/user
admin
$ cat /projected-volume/pass

(2)通过环境变量

#pod-secret-env.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-secret-env
spec:
  containers:
  - name: myapp
image: busybox
args:
    - sleep
    - "86400"
env:
    - name: SECRET_USERNAME
      valueFrom:
        secretKeyRef:
          name: mysecret
          key: user
    - name: SECRET_PASSWORD
      valueFrom:
        secretKeyRef:
          name: mysecret
          key: pass
  restartPolicy: Never

pod运行成功后:

$ kubectl exec -it pod-secret-env -- /bin/sh

进入容器中查看环境变量: env

1.1.1 通过volume挂载和环境变量的区别

通过Volume挂载到容器内部时,当该Secret的值发生变化时,容器内部具备自动更新的能力,但是通过环境变量设置到容器内部该值不具备自动更新的能力。所以一般推荐使用Volume挂载的方式使用Secret。

热更新原理参考附录

1.1.2 Secret 与 ConfigMap 对比

最后我们来对比下Secret和ConfigMap这两种资源对象的异同点:

相同点:

key/value的形式

属于某个特定的namespace

可以导出到环境变量

可以通过目录/文件形式挂载

通过 volume 挂载的配置信息均可热更新

不同点:

Secret 可以被 ServerAccount 关联

Secret 可以存储 docker register 的鉴权信息,用在 ImagePullSecret 参数中,用于拉取私有仓库的镜像

Secret 支持 Base64 加密

Secret 分为 kubernetes.io/service-account-token、kubernetes.io/dockerconfigjson、Opaque 三种类型,而 Configmap 不区分类型

1.2 kubernetes.io/dockerconfigjson

这个是为了应付 pull 私有image时候的权限问题。常见用法是:

(1)创建secrect

$ cat ~/.docker/config.json | base64
$ cat > myregistrykey.yaml <

(2) 将这个secret和serviceaccount绑定

root@cld-kmaster1-1022:/home/ngadm# kubectl get serviceaccount -n test-nsp-gzchenyifan
NAME      SECRETS   AGE
default   1         3h28m


root@cld-kmaster1-1022:/home/ngadm# kubectl get serviceaccount -n test-nsp-gzchenyifan -oyaml
apiVersion: v1
items:
- apiVersion: v1
  imagePullSecrets:
  - name: myregistrykey
  kind: ServiceAccount
  metadata:
    creationTimestamp: "2022-11-03T06:00:30Z"
    name: default
    namespace: test-test
    resourceVersion: "1540683279"
    selfLink: /api/v1/namespaces/test-nsp-gzchenyifan/serviceaccounts/default
    uid: 957739c2-cac9-4bae-bad9-0862ca413dd2
  secrets:
  - name: default-token-tb8xx   //默认的secret
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""
  
// 再查看pod yaml的时候,就会发现指定了这个myregistrykey
 imagePullSecrets:
  - name: nsp-dev

1.3 Service Account类型

Service Account用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中。

$ kubectl run nginx --image nginx
deployment "nginx" created
$ kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
nginx-3137573019-md1u2   1/1       Running   0          13s
$ kubectl exec nginx-3137573019-md1u2 ls /run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token

serviceAccount资源介绍

参考github源码分析 https://github.com/zoux86/learning-k8s-source-code/blob/master/k8s/kube-apiserver/17-k8s%E4%B9%8Bserviceaccount.md

1.4 secret三种类型的原理

其实都是kubelet 的secretpulgin在起作用。如果是dockerconfig类型,他通过拉取secret的值,填充pod的imagePull策略。如果是service account, 他通过拉取sa, token值。

具体代码:pkg/volume/secret/secret.go

3.附录

3.1 K8S Configmap 和 Secret 作为 Volume 的热更新原理

configmap/secret 作为 volume 挂载在容器内,如果 configmap 值发生变化,最大等待时间在 kubelet resyncInterval(60s) 内 该 mount 的 key 就会变成最新值。比如 cilium pod 挂载 cilium-config configmap,如果修改该 configmap 的 debug:false 为 true, 最多等待 60s,容器内该 debug 文件值就是 true。

但是作为环境变量 env 和 volume subpath 不支持热更新,环境变量在初始化过程就固定了。

热更新原理

(1) kubelet 会在每 60s 内去 syncPod(),检查 pod 的 volume kubelet.volumeManager.WaitForAttachAndMount(pod), github.com/kubernetes/… github.com/kubernetes/…

这里重点是 ReprocessPod(),会把这个 pod 又标记为未处理,等待 desiredStateOfWorldPopulator 下一次循环去 MarkRemountRequired()

(2) desiredStateOfWorldPopulator 下一次循环,会走 findAndAddNewPods() -> processPodVolumes() 这里重点是 dswp.actualStateOfWorld.MarkRemountRequired(uniquePodName),在 actual 里 MarkRemountRequired github.com/kubernetes/… 这里会判断每一个 volumePlugin.RequiresRemount(),而对于 configmap/secret volume 是 true,对于 csi 是 false github.com/kubernetes/… github.com/kubernetes/… github.com/kubernetes/…

(3) 然后再下一次循环里去 mountAttachVolumes() PodExistsInVolume() 会走 podObj.remountRequired,因为 MarkRemountRequired() 已经设置了需要 remount,然后 mountAttachVolumes() 里走 MountVolume() 逻辑:github.com/kubernetes/… 这样就走 configmap/secret mount 逻辑。

(4) configmap/secret mount 会使用 emptyDir plugin 来创建落盘目录 configmap 用的 v1.StorageMediumDefault github.com/kubernetes/…

secret 用的 v1.StorageMediumMemory github.com/kubernetes/… , 对于 secret 首次 mount 会使用命令 mount -t tmpfs xxx: github.com/kubernetes/… github.com/kubernetes/…

对于 configmap 这里的 wrapped 是 emptyDir,主要用来创建文件和权限

wrapped, err := b.plugin.host.NewWrapperMounter(b.volName, wrappedVolumeSpec(), &b.pod, *b.opts)
wrapped.SetUpAt(dir, mounterArgs)

// 这里的 getConfigMap 是 configmapManager 的 configMapManager.GetConfigMap()
// https://github.com/kubernetes/kubernetes/blob/v1.19.7/pkg/kubelet/configmap/configmap_manager.go#L82-L91
// 注意,kubelet 默认使用 kubeletconfiginternal.WatchChangeDetectionStrategy 的 configmapManager,所以 configmap
// 发生变化,configmapManager 立刻拿到最新的值:https://github.com/kubernetes/kubernetes/blob/v1.19.7/pkg/kubelet/kubelet.go#L538-L540
// 只是需要等待 kubelet 每次的 resyncInterval 60s 去 syncPod,所以每次修改 configmap 最大等待时间是 60s。
configMap, err := b.getConfigMap(b.pod.Namespace, b.source.Name)

// 然后把最新的 configmap 对象数据写到每一个文件里
payload, err := MakePayload(b.source.Items, configMap, b.source.DefaultMode, optional)
err = writer.Write(payload)

复制代码
参考文献

mounted-configmaps-are-updated-automatically

mounted-configmaps-are-updated-automatically

Kubernetes Pod 中的 ConfigMap 配置更新

分别测试使用 ConfigMap 挂载 Env 和 Volume 的情况

开始只有 NewCachingConfigMapManager(),除了 kubelet resyncInterval 时间还有个 ttl 时间,经过讨论后期加了 NewWatchingConfigMapManager, 直接 watch 立刻拿到最新的 configmap,只需要等待最大 kubelet resyncInterval 时间。下面链接是 issue 和 pr:

Kubelet watches necessary secrets/configmaps instead of periodic polling

Migrate kubelet to ConfigMapManager interface and use TTL-based caching manager

kubelet refresh times for configmaps is long and random

你可能感兴趣的:(k8s secret对象详解)