参考:https://kubernetes.io/zh/docs/concepts/configuration/secret/
k8s Secret介绍
Secret 是一种包含少量敏感信息例如密码、token 或 key 的对象。这样的信息可能会被放在 Pod spec 中或者镜像中;将其放在一个 secret 对象中可以更好地控制它的用途,并降低意外暴露的风险。
用户可以创建 secret,同时系统也创建了一些 secret。
要使用 secret,pod 需要引用 secret。Pod 可以用三种方式使用 secret:作为 volume 中的文件被挂载到 pod 中的一个或者多个容器里;作为环境变量注入;或者当 kubelet 为 pod 拉取镜像时使用。
Secret 有三种类型:
-
kubernetes.io/service-account-token
:这是 Service Account 使用 API 凭证自动创建和附加 secret。Kubernetes 自动创建包含访问 API 凭据的 secret,并自动修改您的 pod 以使用此类型的 secret。如果需要,可以禁用或覆盖自动创建和使用API凭据。但是,如果您需要的只是安全地访问 apiserver,我们推荐这样的工作流程。
-
kubernetes.io/dockerconfigjson
:用来存储私有 docker registry 的认证信息。使用imagePullSecrets
策略指定 Secret。 -
Opaque
:base64 编码格式的 Secret,用来存储密码、密钥等;但数据也可以通过 base64 –decode 解码得到原始数据,所有加密性很弱。
Service Account
这种是 k8s 集群可以自己管理的一种加密数据,如果没有特殊需求,可以不对其进行修改,这里不详细介绍。
Opaque Secret
使用文件创建
假设有些 pod 需要访问数据库。这些 pod 需要使用的用户名和密码在您本地机器的 ./username.txt
和 ./password.txt
文件里。
echo -n 'admin' > ./username.txt
echo -n '1f2d1e2e67df' > ./password.txt
kubectl create secret
命令将这些文件打包到一个 Secret 中并在 API server 中创建了一个对象。
[root@k8s-master01 secret]# kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
secret/db-user-pass created
这时查看 Secret 你会发现,k8s 会自动将明文的 username 和 password 内容改为 base64 编码格式:
[root@k8s-master01 secret]# kubectl get secret
NAME TYPE DATA AGE
db-user-pass Opaque 2 3m38s
default-token-5997c kubernetes.io/service-account-token 3 12d
[root@k8s-master01 secret]# kubectl get secret db-user-pass -o yaml
apiVersion: v1
data:
password.txt: MWYyZDFlMmU2N2Rm
username.txt: YWRtaW4=
kind: Secret
metadata:
creationTimestamp: "2020-03-30T14:35:56Z"
name: db-user-pass
namespace: default
resourceVersion: "267060"
selfLink: /api/v1/namespaces/default/secrets/db-user-pass
uid: 08f3315f-e21e-4680-b54b-a980dc9829e7
type: Opaque
使用 yaml 清单创建
Opaque 类型的数据是一个 map 类型,要求 value 是 base64 编码格式。
下面我们对一个账号密码进行加密:
[root@k8s-master01 secret]# echo -n "admin" | base64
YWRtaW4=
[root@k8s-master01 secret]# echo -n "123456" | base64
MTIzNDU2
创建 mysecret.yaml 文件:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MTIzNDU2
使用 kubectl apply
创建:
[root@k8s-master01 secret]# kubectl apply -f mysecret.yaml
secret/mysecret created
[root@k8s-master01 secret]# kubectl get secret
NAME TYPE DATA AGE
default-token-5997c kubernetes.io/service-account-token 3 12d
mysecret Opaque 2 6s
查看 Secret 依然是 base64 编码格式保存的内容:
[root@k8s-master01 secret]# kubectl get secret mysecret -o yaml
apiVersion: v1
data:
password: MTIzNDU2
username: YWRtaW4=
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"password":"MTIzNDU2","username":"YWRtaW4="},"kind":"Secret","metadata":{"annotations":{},"name":"mysecret","namespace":"default"},"type":"Opaque"}
creationTimestamp: "2020-03-30T14:28:32Z"
name: mysecret
namespace: default
resourceVersion: "266360"
selfLink: /api/v1/namespaces/default/secrets/mysecret
uid: cb6a9cd6-173f-4a50-a061-7a4d3c8542ca
type: Opaque
解码 Secret
从 Secret 中获取到的 base64 编码格式的内容,也可以使用 base64 编码格式转化为明文:
[root@k8s-master01 secret]# kubectl get secret
NAME TYPE DATA AGE
default-token-5997c kubernetes.io/service-account-token 3 12d
mysecret Opaque 2 18m
[root@k8s-master01 secret]# kubectl get secret mysecret -o yaml
apiVersion: v1
data:
password: MTIzNDU2
username: YWRtaW4=
......
[root@k8s-master01 secret]# echo -n "MTIzNDU2" | base64 -d
123456
[root@k8s-master01 secret]# echo -n "YWRtaW4=" | base64 -d
admin
编辑 Secret
可以通过下面的命令编辑一个已经存在的 secret 。
kubectl edit secrets mysecret
这将打开默认配置的编辑器,并允许更新 data
字段中的 base64 编码的 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: MTIzNDU2
username: YWRtaW4=
kind: Secret
......
使用 Secret
作为 volume 使用
Secret 可以作为数据卷被挂载,或作为环境变量暴露出来以供 Pod 中的容器使用。它们也可以被系统的其他部分使用,而不直接暴露在 Pod 内。 例如,它们可以保存凭据,系统的其他部分应该用它来代表您与外部系统进行交互。
在 Pod 中的 volume 里使用 Secret
- 创建一个 secret 或者使用已有的 secret。多个 pod 可以引用同一个 secret。
- 修改 pod 的定义在
spec.volumes[]
下增加一个 volume。可以给这个 volume 随意命名,它的spec.volumes[].secret.secretName
必须等于 secret 对象的名字。 - 将
spec.containers[].volumeMounts[]
加到需要用到该 secret 的容器中。指定spec.containers[].volumeMounts[].readOnly = true
和spec.containers[].volumeMounts[].mountPath
为您想要该 secret 出现的尚未使用的目录。 - 修改您的镜像并且/或者命令行让程序从该目录下寻找文件。Secret 的
data
映射中的每一个键都成为了mountPath
下的一个文件名。
示例:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
volumes:
- name: foo
secret:
secretName: mysecret
containers:
- name: mypod
image: hub.xixihaha.com/library/mynginx:v1
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
想要用的每个 secret 都需要在 spec.volumes
中指明。
如果 pod 中有多个容器,每个容器都需要自己的 volumeMounts
配置块,但是每个 secret 只需要一个 spec.volumes
。
可以打包多个文件到一个 secret 中,或者使用的多个 secret,怎样方便就怎样来。
向特性路径映射 secret 密钥
我们还可以控制 Secret key 映射在 volume 中的路径。您可以使用 spec.volumes[].secret.items
字段修改每个 key 的目标路径:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
volumes:
- name: foo
secret:
secretName: mysecret
items:
- key: username
path: my-group/my-username
containers:
- name: mypod
image: hub.xixihaha.com/library/mynginx:v1
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
效果如下:
username
secret 存储在/etc/foo/my-group/my-username
文件中而不是/etc/foo/username
中。password
secret 没有被映射。
如果使用了 spec.volumes[].secret.items
,只有在 items
中指定的 key 被映射。要使用 secret 中所有的 key,所有这些都必须列在 items
字段中。所有列出的密钥必须存在于相应的 secret 中。否则,不会创建卷。
挂载的 secret 被自动更新
当已经在 volume 中被消费的 secret 被更新时,被映射的 key 也将被更新。 Kubelet 在周期性同步时检查被挂载的 secret 是不是最新的。 但是,它正在使用其本地缓存来获取 Secret 的当前值。
缓存的类型可以使用 (ConfigMapAndSecretChangeDetectionStrategy
中的 KubeletConfiguration 结构). 它可以通过基于 ttl 的 watch(默认)传播,也可以将所有请求直接重定向到直接kube-apiserver。 结果,从更新密钥到将新密钥投射到 Pod 的那一刻的总延迟可能与 kubelet 同步周期 + 缓存传播延迟一样长,其中缓存传播延迟取决于所选的缓存类型。 (它等于观察传播延迟,缓存的ttl或相应为0)。
作为环境变量使用
将 secret 作为 pod 中的环境变量使用:
- 创建一个 secret 或者使用一个已存在的 secret。多个 pod 可以引用同一个 secret。
- 修改 Pod 定义,为每个要使用 secret 的容器添加对应 secret key 的环境变量。消费 secret key 的环境变量应填充 secret 的名称,并键入
env[x].valueFrom.secretKeyRef
。 - 修改镜像并/或者命令行,以便程序在指定的环境变量中查找值。
示例:
apiVersion: v1
kind: Pod
metadata:
name: secret-env-pod
spec:
containers:
- name: mycontainer
image: hub.xixihaha.com/library/mynginx:v1
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
restartPolicy: Never
效果如下:
[root@k8s-master01 secret]# kubectl get pod
NAME READY STATUS RESTARTS AGE
secret-env-pod 1/1 Running 0 2m45s
[root@k8s-master01 secret]# kubectl exec secret-env-pod -it /bin/sh
# echo $SECRET_USERNAME
admin
# echo $SECRET_PASSWORD
123456
ImagePullSecret
假如我们创建 Pod 时,Pod 里面所使用的镜像是私有的,需要进行身份认证才能够拉取,那么我们可以创建一个具有该私有仓库认证信息的 Secret,然后使用 ImagePullSecrets 指定。
下面我们使用 harbor 私有仓库进行举例。在 harbor 中创建一个私有项目 tom,并向该项目中 push 一个镜像 hub.xixihaha.com/tom/nginx:v1
。然后将集群中的所有节点都退出 harbor 的登录,并删除所有节点中的 hub.xixihaha.com/tom/nginx:v1
镜像。
认证失败示例:
apiVersion: v1
kind: Pod
matadata:
name: my-pod
spec:
containers:
- name: nginx-container
image: hub.xixihaha.com/tom/nginx:v1
创建这个 Pod:
[root@k8s-master01 secret]# kubectl create -f my-pod.yaml
pod/my-pod created
[root@k8s-master01 secret]# kubectl get pod
NAME READY STATUS RESTARTS AGE
my-pod 0/1 ImagePullBackOff 0 3s
[root@k8s-master01 secret]# kubectl describe pod my-pod
Name: my-pod
Namespace: default
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 13s default-scheduler Successfully assigned default/my-pod to k8s-node02
Normal Pulling 12s kubelet, k8s-node02 Pulling image "hub.xixihaha.com/tom/nginx:v1"
Warning Failed 12s kubelet, k8s-node02 Failed to pull image "hub.xixihaha.com/tom/nginx:v1": rpc error: code = Unknown desc = Error response from daemon: pull access denied for hub.xixihaha.com/tom/nginx, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
Warning Failed 12s kubelet, k8s-node02 Error: ErrImagePull
Normal BackOff 10s (x2 over 11s) kubelet, k8s-node02 Back-off pulling image "hub.xixihaha.com/tom/nginx:v1"
Warning Failed 10s (x2 over 11s) kubelet, k8s-node02 Error: ImagePullBackOff
从上面信息中很容易知道是因为没有权限拉取镜像导致的失败。
创建认证信息 Secret
kubectl create secret docker-registry my-registry-secret --docker-server=hub.xixihaha.com --docker-username=admin --docker-password=Harbor12345 --docker-email=admin@example.com
说明:
docker-registry
:固定字段。my-registry-secret
:该 secret 的名称。
在 yaml 中添加 ImagePullSecrets 信息:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: nginx-container
image: hub.xixihaha.com/tom/nginx:v1
imagePullSecrets:
- name: my-registry-secret
创建 Pod ,可以发现已经正常拉取镜像并运行:
[root@k8s-master01 secret]# kubectl create -f my-pod.yaml
pod/my-pod created
[root@k8s-master01 secret]# kubectl get pod
NAME READY STATUS RESTARTS AGE
my-pod 1/1 Running 0 7s
-- end --