本文共 14891 字,大约阅读时间需要 49 分钟。
Secret的主要作用是保管私密数据,比如密码,OAuth Tokens,SSH Key等信息。这些私密信息放在Secret对象中比直接放在Pod或Docker Image中更加安全,也更方便管理。
Secret一旦被创建,则可以通过以下三种方式来使用:
Kubernetes会自动创建包含用于访问API的凭据的Secret,并自动修改您的pod以使用此类Secret。这是Kubernetes 默认的行为,我们也可以通过自定义的方式禁用或者创建我们所需要的Secret。
如果在集群中,pod需要访问数据库,那么我们可以使用文件的方式定义一个Secret,在本地主机上创建一个./username.txt和./password.txt的文件:
# echo "admin" > ./username.txt # echo "123456" > ./password.txt # kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
查看Secret信息:
# kubectl get secret/db-user-pass -o wideNAME TYPE DATA AGEdb-user-pass Opaque 2 1m# kubectl describe secrets/db-user-passName: db-user-passNamespace: defaultLabels:Annotations: Type: OpaqueData====password.txt: 7 bytesusername.txt: 6 bytes
这里并不会显示文件中的具体信息。
使用YMAL创建Secert时,每个账号密码的项目必须是转换为base64的编码。
[root@node-1 secret]# echo -n 'admin' | base64YWRtaW4=[root@node-1 secret]# echo -n '123456' | base64 # 不能有空格或换行符,或使用 base64 | tr -d '\n'MTIzNDU2
创建YAML文件:
apiVersion: v1kind: Secretmetadata: name: mysecrettype: Opaquedata: username: YWRtaW4= # 账号密码使用base64的编码方式 password: MTIzNDU2
查看具体信息:
[root@node-1 secret]# kubectl get secret mysecret -o yamlapiVersion: v1data: password: MTIzNDU2 username: YWRtaW4=kind: Secretmetadata: creationTimestamp: 2018-07-18T09:02:20Z name: mysecret namespace: default resourceVersion: "898655" selfLink: /api/v1/namespaces/default/secrets/mysecret uid: 47369a09-8a69-11e8-9c5f-000c295f81fbtype: Opaque
如果要查看密码,可以对编码解密:
# echo 'YWRtaW4=' | base64 --decodeadmin# echo 'MTIzNDU2' | base64 -d123456
当创建好一个Secret后,可以通过多种方式使用Secret,一般有如下场景:
通过如下步骤:
这里给出一个示例:
apiVersion: v1kind: Podmetadata: name: mypodspec: containers: - name: mypod image: redis volumeMounts: - name: foo mountPath: "/etc/foo" readOnly: true volumes: - name: foo secret: secretName: mysecret
在使用Secret时,需要注意以下几点:
我们也可以控制Secret的key文件在挂载卷中的映射路径,可以使用.spec.volumes[].secret.items来指定key的路径:
apiVersion: v1kind: Podmetadata: name: mypodspec: containers: - name: mypod image: redis volumeMounts: - name: foo mountPath: "/etc/foo" readOnly: true volumes: - name: foo secret: secretName: mysecret items: # 这里为usrname的key指定了一个单独的路径 /etc/foo/my-group/my-username - key: username path: my-group/my-username # password 将使用默认的/etc/foo/password
如果使用.spec.volumes[].secret.items参数,那么只有在items中被指定的key才能被投射到指定的目录。要使用Secret中的所有key,所有key都必须列在items字段中。 所有列出的key必须存在于相应的Secret中。 否则,不会创建volume。
我们可以设置Secret的文件的权限,默认情况下文件的权限是0644. 我们可以根据需要,指定整个挂载目录的权限来覆盖单个Key文件的权限:
apiVersion: v1kind: Podmetadata: name: mypodspec: containers: - name: mypod image: redis volumeMounts: - name: foo mountPath: "/etc/foo" volumes: - name: foo secret: secretName: mysecret defaultMode: 256 # 这里是使用十进制表示的,转换为8进制为0400
这样在指定的挂载卷下,所有被Secret创建的文件都有400的权限。需要注意的是,JSON参数不支持八进制的表示方式,所以使用十进制的256方式来表示400权限。但是在使用YAML格式的文件定义Pod时可以正常使用八进制的表示方式。
同理,也可以指定key的路径,并分配权限:
apiVersion: v1kind: Podmetadata: name: mypodspec: containers: - name: mypod image: redis volumeMounts: - name: foo mountPath: "/etc/foo" volumes: - name: foo secret: secretName: mysecret items: - key: username path: my-group/my-username mode: 511 #这里表示0777的权限。
在安装Secret卷的容器内,keys显示为文件,key值基于64进行解码并存储在这些文件中。 但是在这些文件中是以明文方式存储的,容器中的程序负责从文件中读取秘密。
即使已经被挂载到卷中使用的Secret,也依然会随着Secret的更新而自动更新。 Kubelet将会在每个同步周期内检测当前被挂载的Secret是否是最新的。
容器中使用subPath volume 方式挂载的Secret将不会被更新。
使用环境变量的方式有如下特性:
如下示例:
apiVersion: v1kind: Podmetadata: name: secret-env-podspec: containers: - name: mycontainer image: redis env: - name: SECRET_USERNAME valueFrom: secretKeyRef: name: mysecret key: username - name: SECRET_PASSWORD valueFrom: secretKeyRef: name: mysecret key: password restartPolicy: Never
在此示例中,Secret会以环境变量的方式加载到容器中:
$ echo $SECRET_USERNAMEadmin$ echo $SECRET_PASSWORD123456
在设置的私有仓库中拉取镜像时,我们可以使用imagePullSecrets来作为仓库的登录密码。
可以通过两种方式来使用imagePullSecrets:
使用命令行创建一个ImagePullSecret, 将大写的部分改为具体的参数:
kubectl create secret docker-registry myregistrykey --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAILsecret "myregistrykey" created.
如果需要访问多个私有仓库,可以为每个仓库创建一个Secret, 当拉取镜像时,Kubelet会合并所有的imagePullSecrets到一个虚拟的.docker/config.json文件中。
Pods拉取镜像只能在自己的命名空间中识别Secrets,所以在有多个namespaces的情况下,需要给每namespace都执行相同的操作。
还可以使用yaml或json文件的方式来创建ImagePullSecret。不过需要注意以下三点:
通过如下步骤配置:
# docker login localhost:5000
登录成功之后,会创建一个.docker/config.json的文件,此文件包含了登录的账号密码信息:
# cat .docker/config.json { "auths": { "localhost:5000": { "auth": "dGVzdDp0ZXN0MTIzCg==" # 使用base64方式编码的账号密码 } }, "HttpHeaders": { "User-Agent": "Docker-Client/17.09.1-ce (linux)" }}
# cat /root/.docker/config.json | base64 ewoJImF1dGhzIjogewoJCSJsb2NhbGhvc3Q6NTAwMCI6IHsKCQkJImF1dGgiOiAiZEdWemREcDBaWE4wTVRJekNnPT0iICAjIOS9v+eUqGJhc2U2NOaWueW8j+e8lueggeeahOi0puWPt+WvhueggQoJCX0KCX0sCgkiSHR0cEhlYWRlcnMiOiB7CgkJIlVzZXItQWdlbnQiOiAiRG9ja2VyLUNsaWVudC8xNy4wOS4xLWNlIChsaW51eCkiCgl9Cn0K
apiVersion: v1kind: Secretmetadata:name: myregistrykeynamespace: awesomeappsdata:.dockerconfigjson: ewoJImF1dGhzIjogewoJCSJsb2NhbGhvc3Q6NTAwMCI6IHsKCQkJImF1dGgiOiAiZEdWemREcDBaWE4wTVRJekNnPT0iICAjIOS9v+eUqGJhc2U2NOaWueW8j+e8lueggeeahOi0puWPt+WvhueggQoJCX0KCX0sCgkiSHR0cEhlYWRlcnMiOiB7CgkJIlVzZXItQWdlbnQiOiAiRG9ja2VyLUNsaWVudC8xNy4wOS4xLWNlIChsaW51eCkiCgl9Cn0Ktype: kubernetes.io/dockerconfigjson
提示:
apiVersion: v1kind: Podmetadata: name: foo namespace: awesomeappsspec: containers: - name: foo image: janedoe/awesomeapp:v1 imagePullSecrets: - name: myregistrykey
# kubectl get secrets myregistrykeyNAME TYPE DATA AGEmyregistrykey kubernetes.io/.dockerconfigjson 1 1d# kubectl patch serviceaccount default -p '{\"imagePullSecrets\": [{\"name\": \"myregistrykey\"}]}'
在默认的Service account的命名空间中使用这个Secret,并将它作为ImagePullSecret。
kubectl get serviceaccounts default -o yaml > ./sa.yaml
$ cat sa.yamlapiVersion: v1kind: ServiceAccountmetadata: creationTimestamp: 2015-08-07T22:02:39Z name: default namespace: default selfLink: /api/v1/namespaces/default/serviceaccounts/default uid: 052fb0f4-3d50-11e5-b066-42010af0d7b6secrets:- name: default-token-uudgeimagePullSecrets:- name: myregistrykey
4.重新生成 ServiceAccount:
$ kubectl replace serviceaccount default -f ./sa.yamlserviceaccounts/default
经过这样的配置之后,在当前namespaces中新创建的Pod都会自动添加如下参数:
spec: imagePullSecrets: - name: myregistrykey
kubectl
命令创建的Pods和通过RC直接创建的Pods。不包含使用kubelet --manifest-url
参数, --config
参数创建的Pod,使用REST API直接创建的也无法使用。$ kubectl get eventsLASTSEEN FIRSTSEEN COUNT NAME KIND SUBOBJECT TYPE REASON0s 0s 1 dapi-test-pod Pod Warning InvalidEnvironmentVariableNames kubelet, 127.0.0.1 Keys [1badkey, 2alsobad] from the EnvFrom secret default/mysecret were skipped since they are considered invalid environment variable names.
通过API Server创建pod时,API Sserver不会检查是否存在可以引用的Secret。 一旦pod被调度,kubelet将尝试获取Secret值。 如果由于该Secret不存在或由于暂时缺少与API服务器的连接而无法获取该Secret,则kubelet将定期重试。 它将报告有关pod的事件,说明它尚未启动的原因。 获取Secret后,kubelet将创建并安装包含它的卷。 在安装所有pod的卷之前,pod的容器都不会启动。
在Kubelet启动Pod中的Container后,Container中和Secret相关的Volume将不会被改变,即使Secret本身被修改了。如果要使用更新后的Secret,必须删除旧的Pod,并重新创建一个新的Pod.创建一个包含SSH Key的Secret:
kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
提示:在发送自己的ssh密钥之前要仔细考虑:群集的其他用户可以访问该Secret。 使用您希望与您共享Kubernetes群集的所有用户都可以访问的Service Account,如果他们被泄露,可以撤销此Secret.
创建一个Pod,使用此Secret:
kind: PodapiVersion: v1metadata: name: secret-test-pod labels: name: secret-testspec: volumes: - name: secret-volume secret: secretName: ssh-key-secret containers: - name: ssh-test-container image: mySshImage volumeMounts: - name: secret-volume readOnly: true mountPath: "/etc/secret-volume"
当容器的命令运行时,指定的key将会在如下目录被使用:
/etc/secret-volume/ssh-publickey/etc/secret-volume/ssh-privatekey
然后容器可以自由地使用Secret数据来建立ssh连接。
在不同环境中,可以指定不同的Secret,比如生产和测试Secret的进行区分。
首先,创建生产,测试两个不同的Secret:$ kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11secret "prod-db-secret" created$ kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtestssecret "test-db-secret" created
注意:特殊字符如 '$', '*', '!'需要转义。如果密码中包含特殊字符,需要使用 \ 来进行转义,如,使用密码为:S!B*d$zDsb ,那需要执行如下命令:
kubectl create secret generic dev-db-secret --from-literal=username=devuser --from-literal=password=S\\!B\\\*d\\$zDsb如果是以指定文件的方式,使用‘--from-file’的方式,就不需要转义。
创建Pod:
apiVersion: v1kind: Listitems:- kind: Pod apiVersion: v1 metadata: name: prod-db-client-pod labels: name: prod-db-client spec: volumes: - name: secret-volume secret: secretName: prod-db-secret containers: - name: db-client-container image: myClientImage volumeMounts: - name: secret-volume readOnly: true mountPath: "/etc/secret-volume"- kind: Pod apiVersion: v1 metadata: name: test-db-client-pod labels: name: test-db-client spec: volumes: - name: secret-volume secret: secretName: test-db-secret containers: - name: db-client-container image: myClientImage volumeMounts: - name: secret-volume readOnly: true mountPath: "/etc/secret-volume"
两个Container中都会文件系统中找到如下文件,其中包含不同容器环境的值:
/etc/secret-volume/username/etc/secret-volume/password
请注意两个pod的规格如何仅在一个字段中有所不同; 这有助于从常见的pod配置模板创建具有不同功能的pod。
您可以使用两个服务帐户进一步简化基本容器规范:一个称为prod-user,使用prod-db-secret,另一个称为test-user,带有test-db-secret。 然后,pod规范可以缩短为,例如:kind: PodapiVersion: v1metadata: name: prod-db-client-pod labels: name: prod-db-clientspec: serviceAccount: prod-db-client containers: - name: db-client-container image: myClientImage
如果想在Pod中使用.filename的方式隐藏密钥文件,可以使用如下的定义方式:
kind: SecretapiVersion: v1metadata: name: dotfile-secretdata: .secret-file: dmFsdWUtMg0KDQo=---kind: PodapiVersion: v1metadata: name: secret-dotfiles-podspec: volumes: - name: secret-volume secret: secretName: dotfile-secret containers: - name: dotfile-test-container image: k8s.gcr.io/busybox command: - ls - "-l" - "/etc/secret-volume" volumeMounts: - name: secret-volume readOnly: true mountPath: "/etc/secret-volume"
secret-volume中将会包换一个单独的文件 .secret-file , 在dotfile-test-container容器中将会在如下路径存放此文件:
/etc/secret-volume/.secret-file
考虑到应用程序需要回应HTTP请求,处理一些复杂的业务逻辑,使用HMAC签名的方式加密消息。由于应用逻辑的复杂性,在服务器中可能存在未被注意的远程文件读取漏洞,这可能会将私钥暴露给×××者。
这可以分为两个容器中的两个进程:一个前端容器,它处理用户交互和业务逻辑,但是看不到私钥; 和一个可以看到私钥的签名者容器,并响应来自前端的简单签名请求(例如,通过localhost网络)使用这种分区方法,×××者现在必须欺骗应用程序服务器做一些相当随意的事情,这可能比让它读取文件更困难。当部署与Secret API进行交互的应用时,应使用如RBAC等认证策略加以限制。
Secret通常具有跨越一系列重要性的价值,其中许多可能导致Kubernetes集群发生更改。
由于这些原因,使用wathc和list在namespace中操作Secret是非常高的的权限,我们应该尽量避免普通用户有此权限。应该仅为最高特权的系统级组件保留查看和列出群集中所有Secret的功能。
需要访问Secret API的应用程序应该根据需要执行get请求。 这使管理员可以限制对所有Secret 的访问,同时白名单列出对应用所需的各个实例的访问权限。
为了提高循环get的性能,客户端可以设计引用Secret的资源,然后使用watch此资源,在引用更改时重新请求Secret。此外,还提出了一种允许客户端watch单个资源的“bulk watch ”API,并且可能会在未来的Kubernetes版本中提供。
由于可以独立于使用它们的pod创建Secret对象,因此在创建,查看和编辑pod的工作流程中泄露Secret的风险较小。系统还可以对Secret对象采取额外的保护措施,例如尽可能避免将其写入磁盘。
如果该节点上的pod需要使用Secret,则仅将Secret发送到此节点,并且Secret不会被写入磁盘。它存储在tmpfs中。一旦删除依赖它的pod,它就会被删除。
在大多数Kubernetes项目维护的发行版中,用户与apiserver之间的通信以及从apiserver到kubelet的通信受SSL / TLS保护。通过这些通道传输时,有效保护了Secret。
节点上的Secret数据存储在tmpfs卷中,因此不会停留在节点上。
在同一节点上可能存在几个pod的Secret。但是,只有pod请求的Secret可能在其容器中可见。因此,一个Pod无法访问另一个pod的Secret。
Pod中可能有几个容器。但是,pod中的每个容器都必须在其volumeMounts中请求Secret卷,以使其在容器中可见。这可用于在Pod级别构建有用的安全分区。
提示:从1.7版本开始,已经支持在REST中对Secret数据加密。
转载于:https://blog.51cto.com/tryingstuff/2147789