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 <<EOF
apiVersion: v1
kind: Secret
metadata:
name: myregistrykey
data:
.dockerconfigjson: UmVhbGx5IHJlYWxseSByZWVlZWVlZWVlZWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGx5eXl5eXl5eXl5eXl5eXl5eXl5eSBsbGxsbGxsbGxsbGxsbG9vb29vb29vb29vb29vb29vb29vb29vb29vb25ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubmdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2cgYXV0aCBrZXlzCg==
type: kubernetes.io/dockerconfigjson
EOF
$ kubectl create -f 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