Secret
Secret
前文我们学习 ConfigMap 的时候,我们说 ConfigMap 这个资源对象是 Kubernetes 当中非常重要的一个资源对象,一般情况下 ConfigMap 是用来存储一些非安全的配置信息,如果涉及到一些安全相关的数据的话用 ConfigMap 就非常不妥了,因为 ConfigMap 是明文存储的,这个时候我们就需要用到另外一个资源对象了:Secret,Secret用来保存敏感信息,例如密码、OAuth 令牌和 ssh key 等等,将这些信息放在 Secret 中比放在 Pod 的定义中或者 Docker 镜像中要更加安全和灵活。
Secret
主要使用的有以下几种类型:
-
Opaque:base64 编码格式的 Secret,用来存储密码、密钥等;但数据也可以通过
base64 –decode
解码得到原始数据,所有加密性很弱。 -
kubernetes.io/dockercfg
:~/.dockercfg
文件的序列化形式 -
kubernetes.io/dockerconfigjson
:用来存储私有registry
的认证信息,~/.docker/config.json
文件的序列化形式 -
kubernetes.io/service-account-token
:用于ServiceAccount
,ServiceAccount
创建时Kubernetes
会默认创建一个对应的Secret
对象,Pod
如果使用了ServiceAccount
,对应的Secret
会自动挂载到Pod
目录/run/secrets/kubernetes.io/serviceaccount
中 -
kubernetes.io/ssh-auth
:用于 SSH 身份认证的凭据 -
kubernetes.io/basic-auth
:用于基本身份认证的凭据 -
bootstrap.kubernetes.io/token
:用于节点接入集群的校验的 Secret
Opaque Secret
Secret 资源包含2个键值对: data 和 stringData,data 字段用来存储 base64 编码的任意数据,提供 stringData 字段是为了方便,它允许 Secret 使用未编码的字符串。 data 和 stringData 的键必须由字母、数字、-,_ 或 . 组成。
比如我们来创建一个用户名为 admin,密码为 admin321 的 Secret 对象,首先我们需要先把用户名和密码做 base64 编码:
➜ ~ echo -n "admin" | base64
YWRtaW4=
➜ ~ echo -n "admin321" | base64
YWRtaW4zMjE=
然后我们就可以利用上面编码过后的数据来编写一个 YAML 文件:(secret-demo.yaml)
# cat secret-demo.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: YWRtaW4zMjE=
进行 apply 操作,后查看 secret 信息
[root@kube01 secret]# kubectl apply -f secret-demo.yaml
secret/mysecret created
[root@kube01 secret]# kubectl describe secret mysecret
Name: mysecret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 8 bytes
username: 5 bytes
我们可以看到 describe 看不到密钥信息,需要通过 get -o yaml 显示
[root@kube01 secret]# kubectl get secret mysecret -o yaml
apiVersion: v1
data:
password: YWRtaW4zMjE=
username: YWRtaW4=
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"password":"YWRtaW4zMjE=","username":"YWRtaW4="},"kind":"Secret","metadata":{"annotations":{},"name":"mysecret","namespace":"default"},"type":"Opaque"}
creationTimestamp: "2023-06-28T02:56:55Z"
name: mysecret
namespace: default
resourceVersion: "4161717"
uid: bdea7a3f-0b8e-4d2f-a143-fdbf61f6d709
type: Opaque
对于某些场景,你可能希望使用 stringData 字段,这字段可以将一个非 base64 编码的字符串直接放入 Secret 中, 当创建或更新该 Secret 时,此字段将被编码。
比如当我们部署应用时,使用 Secret 存储配置文件, 你希望在部署过程中,填入部分内容到该配置文件。例如,如果你的应用程序使用以下配置文件:
apiUrl: "https://my.api.com/api/v1"
username: "<user>"
password: "<password>"
那么我们就可以使用以下定义将其存储在 Secret 中:
# cat stringData.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
stringData:
config.yaml: |
apiUrl: "https://my.api.com/api/v1"
username: <user>
password: <password>
查看这个 secret 会发现config.yaml 文件内容已经被 base64 编码转换
[root@kube01 secret]# kubectl get secret mysecret -o yaml
apiVersion: v1
data:
config.yaml: YXBpVXJsOiAiaHR0cHM6Ly9teS5hcGkuY29tL2FwaS92MSIKdXNlcm5hbWU6IDx1c2VyPgpwYXNzd29yZDogPHBhc3N3b3JkPgo=
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"mysecret","namespace":"default"},"stringData":{"config.yaml":"apiUrl: \"https://my.api.com/api/v1\"\nusername: \u003cuser\u003e\npassword: \u003cpassword\u003e\n"},"type":"Opaque"}
creationTimestamp: "2023-06-28T02:56:55Z"
name: mysecret
namespace: default
resourceVersion: "4162380"
uid: bdea7a3f-0b8e-4d2f-a143-fdbf61f6d709
type: Opaque
创建好 Secret对象后,有两种方式来使用它:
- 以环境变量的形式
- 以Volume的形式挂载
环境变量
以环境变量形式使用
# cat env-secret-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: secret1-pod
spec:
containers:
- name: secret1
image: busybox
command: [ "/bin/sh", "-c", "env" ]
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
主要需要注意的是上面环境变量中定义的 secretKeyRef 字段,和我们前文的 configMapKeyRef 类似,一个是从 Secret 对象中获取,一个是从 ConfigMap 对象中获取,创建上面的 Pod:
[root@kube01 secret]# kubectl apply -f env-secret-pod.yaml
pod/secret1-pod created
[root@kube01 secret]# kubectl logs -f secret1-pod
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.254.0.1:443
HOSTNAME=secret1-pod
SHLVL=1
HOME=/root
USERNAME=admin
KUBERNETES_PORT_443_TCP_ADDR=10.254.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP=tcp://10.254.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_HOST=10.254.0.1
PWD=/
PASSWORD=admin321
可以发现使用环境变量的形式已经被读取到
Volume 挂载
以 volume 挂载形式使用
# cat volume-secret-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: secret2-pod
spec:
containers:
- name: secret2
image: busybox
command: ["/bin/sh", "-c", "ls /etc/secrets"]
volumeMounts:
- name: secrets
mountPath: /etc/secrets
volumes:
- name: secrets
secret:
secretName: mysecret
通过 apply 后查看 log 日志,发现挂载的两个环境变量成了独立的文件,当然如果想要挂载到指定的文件上面,是不是也可以使用上一节课的方法:在 secretName 下面添加 items 指定 key 和 path,这个大家可以参考上节课 ConfigMap 中的方法去测试下。
[root@kube01 secret]# kubectl apply -f volume-secret-pod.yaml
pod/secret2-pod created
[root@kube01 secret]# kubectl logs -f secret2-pod
password
username
kubernetes.io/dockerconfigjson
除了上面的 Opaque 这种类型外,我们还可以来创建用户 docker registry 认证的 Secret,直接使用`kubectl create 命令创建即可,如下:
kubectl create secret docker-registry myregistry --docker-server=DOCKER_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
除了上面这种方法之外,我们也可以通过指定文件的方式来创建镜像仓库认证信息,需要注意对应的 KEY 和 TYPE:
kubectl create secret generic myregistry --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson
如果我们需要拉取私有仓库中的 Docker 镜像的话就需要使用到上面的 myregistry 这个 Secret:
apiVersion: v1
kind: Pod
metadata:
name: foo
spec:
containers:
- name: foo
image: 192.168.1.100:5000/test:v1
imagePullSecrets: # 此处是给 kubelet 使用的
- name: myregistry
ImagePullSecrets 与 Secrets 不同,因为 Secrets 可以挂载到 Pod 中,但是 ImagePullSecrets 只能由 Kubelet 访问。
kubernetes.io/basic-auth
该类型用来存放用于基本身份认证所需的凭据信息,使用这种 Secret 类型时,Secret 的 data 字段(不一定)必须包含以下两个键(相当于是约定俗成的一个规定):
username
: 用于身份认证的用户名password
: 用于身份认证的密码或令牌
以上两个键的键值都是 base64 编码的字符串。 然你也可以在创建 Secret 时使用 stringData 字段来提供明文形式的内容。下面的 YAML 是基本身份认证 Secret 的一个示例清单:
apiVersion: v1
kind: Secret
metadata:
name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
username: admin
password: admin321
提供基本身份认证类型的 Secret 仅仅是出于用户方便性考虑,我们也可以使用 Opaque 类型来保存用于基本身份认证的凭据,不过使用内置的 Secret 类型的有助于对凭据格式进行统一处理
kubernetes.io/ssh-auth
该类型用来存放 SSH 身份认证中所需要的凭据,使用这种 Secret 类型时,你就不一定必须在其 data(或 stringData)字段中提供一个 ssh-privatekey 键值对,作为要使用的 SSH 凭据。
apiVersion: v1
kind: Secret
metadata:
name: secret-ssh-auth
type: kubernetes.io/ssh-auth
data:
ssh-privatekey: |
MIIEpQIBAAKCAQEAulqb/Y ...
同样提供 SSH 身份认证类型的 Secret 也仅仅是出于用户方便性考虑,我们也可以使用 Opaque 类型来保存用于 SSH 身份认证的凭据,只是使用内置的 Secret 类型的有助于对凭据格式进行统一处理。
kubernetes.io/tls
该类型用来存放证书及其相关密钥(通常用在 TLS 场合)。此类数据主要提供给 Ingress 资源,用以校验 TLS 链接,当使用此类型的 Secret 时,Secret 配置中的 data (或 stringData)字段必须包含 tls.key 和 tls.crt主键。下面的 YAML 包含一个 TLS Secret 的配置示例:
apiVersion: v1
kind: Secret
metadata:
name: secret-tls
type: kubernetes.io/tls
data:
tls.crt: |
MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
tls.key: |
MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...
提供 TLS 类型的 Secret 仅仅是出于用户方便性考虑,我们也可以使用 Opaque 类型来保存用于 TLS 服务器与/或客户端的凭据。不过,使用内置的 Secret 类型的有助于对凭据格式进行统一化处理。当使用 kubectl 来创建 TLS Secret 时,我们可以像下面的例子一样使用 tls 子命令:
kubectl create secret tls my-tls-secret \
--cert=path/to/cert/file \
--key=path/to/key/file
需要注意的是用于 --cert 的公钥证书必须是 .PEM 编码的 (Base64 编码的 DER 格式),且与 --key 所给定的私钥匹配,私钥必须是通常所说的 PEM 私钥格式,且未加密。对这两个文件而言,PEM 格式数据的第一行和最后一行(例如,证书所对应的 --------BEGIN CERTIFICATE----- 和 -------END CERTIFICATE----)都不会包含在其中。
kubernetes.io/service-account-token
另外一种 Secret 类型就是 kubernetes.io/service-account-token,用于被 ServiceAccount 引用。ServiceAccout 创建时 Kubernetes 会默认创建对应的 Secret,如下所示我们随意创建一个 Pod:
➜ ~ kubectl run secret-pod3 --image nginx:1.7.9
deployment.apps "secret-pod3" created
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
...
secret-pod3-78c8c76db8-7zmqm 1/1 Running 0 13s
...
我们可以直接查看这个 Pod 的详细信息:
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-lvhfb
readOnly: true
......
serviceAccount: default
serviceAccountName: default
volumes:
- name: kube-api-access-lvhfb
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
当创建 Pod 的时候,如果没有指定 ServiceAccount,Pod 则会使用命名空间中名为 default 的 ServiceAccount,上面我们可以看到 spec.serviceAccountName 字段已经被自动设置了。
可以看到这里通过一个 projected 类型的 Volume 挂载到了容器的 /var/run/secrets/kubernetes.io/serviceaccount 的目录中,projected 类型的 Volume 可以同时挂载多个来源的数据,这里我们挂载了一个 downwardAPI 来获取 namespace,通过 ConfigMap 来获取 ca.crt 证书,然后还有一个 serviceAccountToken 类型的数据源。
前面我们也提到了默认情况下当前 namespace 下面的 Pod 会默认使用 default 这个 ServiceAccount,对应的 Secret 会自动挂载到 Pod 的 /var/run/secrets/kubernetes.io/serviceaccount/ 目录中,这样我们就可以在 Pod 里面获取到用于身份认证的信息了。
我们可以使用自动挂载给 Pod 的 ServiceAccount 凭据访问 API,我们也可以通过在 ServiceAccount 上设置 automountServiceAccountToken: false 来实现不给 ServiceAccount 自动挂载 API 凭据:
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-robot
automountServiceAccountToken: false
...
此外也可以选择不给特定 Pod 自动挂载 API 凭据:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
serviceAccountName: build-robot
automountServiceAccountToken: false
...
如果 Pod 和 ServiceAccount 都指定了 automountServiceAccountToken 值,则 Pod 的 spec 优先于 ServiceAccount。
Secret vs ConfigMap
最后我们来对比下 Secret 和 ConfigMap这两种资源对象的异同点:
相同点
- key/value的形式
- 属于某个特定的命名空间
- 可以导出到环境变量
- 可以通过目录/文件形式挂载
- 通过 volume 挂载的配置信息均可热更新
不同点
- Secret 可以被 ServerAccount 关联
- Secret 可以存储 docker register 的鉴权信息,用在 ImagePullSecret 参数中,用于拉取私有仓库的镜像
- Secret 支持 Base64 加密
- Secret 分为 kubernetes.io/service-account-token、kubernetes.io/dockerconfigjson、Opaque 三种类型,而 Configmap 不区分类型
同样 Secret 文件大小限制为 1MB(ETCD 的要求);Secret 虽然采用 Base64 编码,但是我们还是可以很方便解码获取到原始信息,所以对于非常重要的数据还是需要慎重考虑,可以考虑使用 Vault 来进行加密管理。