基于 Kubernetes 的云原生 DevOps 第 10 章 配置和机密数据
🏷️ Kubernetes 《基于 Kubernetes 的云原生 DevOps》
If you want to keep a secret, you must also hide it from yourself.
-- George Orwell, 1984
Kubernetes 提供了几种不同的方法来帮助管理配置:
- 通过 Pod 规范中的环境变量将值传递给应用程序;
- 使用 ConfigMap 和 Secret 对象将配置数据直接存储在 Kubernetes 中;
10.1 ConfigMap
ConfigMap 是 Kubernetes 中存储配置数据的主要对象。你可以视其为存储配置数据的一组命名键值对。
创建 ConfigMap
假设你需要在 Pod 的文件系统中创建一个名为 config.yaml 的 YAML 配置文件:
autoSaveInterval: 60
batchSize: 18
protocols:
- http
- https
可以直接将文件中的内容原封不动的嵌入到 ConfigMap 配置中,书中示例的格式如下:
apiVersion: v1
data:
config.yaml: |
autoSaveInterval: 60
batchSize: 18
protocols:
- http
- https
kind: ConfigMap
metadata:
name: demo-config
namespace: demo
注意: YAML 中的 |
表示后面是原始数据块。
JiaJia:
现在项目使用的是阿里云,其 ConfigMap 格式如下:
yamlapiVersion: v1 data: autoSaveInterval: '60' batchSize: '18' kind: ConfigMap metadata: name: demo-config namespace: demo
纯数字的话需要用单引号括起来,数组方式貌似不支持。
还有一种更简单的方法,可以通过 kubectl 利用 YAML 文件创建 ConfigMap 。
PS C:\k8s> kubectl create configmap demo-config --namespace=demo --from-file=config.yaml
configmap/demo-config created
创建的配置文件内容如下:
kind: ConfigMap
apiVersion: v1
metadata:
name: demo-config
namespace: demo
uid: 7f34afba-b490-47d3-8828-e4d635391358
resourceVersion: '899820'
creationTimestamp: '2021-12-30T07:41:03Z'
managedFields:
- manager: kubectl-create
operation: Update
apiVersion: v1
time: '2021-12-30T07:41:03Z'
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:config.yaml: {}
data:
config.yaml: "autoSaveInterval: 60\r\nbatchSize: 18\r\nprotocols:\r\n - http\r\n - https"
应该是和上面书中的示例是等价的。
导出 ConfigMap 对应的清单文件:
kubectl get configmap/demo-config --namespace=demo -o yaml > demo-config.yaml
导出文件 demo-config.yaml 内容如下:
apiVersion: v1
data:
config.yaml: "autoSaveInterval: 60\r\nbatchSize: 18\r\nprotocols:\r\n - http\r\n
\ - https"
kind: ConfigMap
metadata:
creationTimestamp: "2021-12-30T07:41:03Z"
name: demo-config
namespace: demo
resourceVersion: "899820"
uid: 7f34afba-b490-47d3-8828-e4d635391358
利用 ConfigMap 设置环境变量
main.go:
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
greeting := os.Getenv("GREETING")
fmt.Fprintf(w, "%s, 世界\n", greeting)
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8888", nil))
}
configmap.yaml:
这里的格式和我这边现行项目的格式一致,直接在 data 中配置的键值对。
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-config
data:
greeting: Hola
deployment.yaml:
在部署的规格中,通过 env 指定环境变量,这里的区别是用 valueFrom 替代了 value,然后通过 configMapKeyRef 中的配置标识环境变量的值来自哪个 ConfigMap 中的哪个 key 。
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
spec:
replicas: 1
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- name: demo
image: cloudnatived/demo:hello-config-env
ports:
- containerPort: 8888
env:
- name: GREETING
valueFrom:
configMapKeyRef:
name: demo-config
key: greeting
kubectl apply -f .\hello-config-env\k8s\
configmap/demo-config created
deployment.apps/demo created
kubectl port-forward deploy/demo 9999:8888
GET http://localhost:9999/
---
HTTP/1.1 200 OK
Date: Thu, 30 Dec 2021 08:28:29 GMT
Content-Length: 13
Content-Type: text/plain; charset=utf-8
Connection: close
Hola, 世界
利用 ConfigMap 设置整个环境
使用 envFrom 获取 ConfigMap 中所有的键,并将其转换为环境变量。
环境变量的 key 就是 ConfigMap 中指定的 key 。
spec:
containers:
- name: demo
image: cloudnatived/demo:hello-config-env
ports:
- containerPort: 8888
envFrom:
- configMapRef:
name: demo-config
使用 envFrom 的同时仍然可以使用 env 指定环境变量,并且同名时 env 中的优先级更高。
在命令参数中指定环境变量
可以使用 Kubernetes 的特殊语法 $(VARIABLE)
在命令行(容器入口)中引用环境变量。
args 中指定的参数会传送到容器的默认入口点。
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
spec:
replicas: 1
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- name: demo
image: cloudnatived/demo:hello-config-args
args:
- "-greeting"
- "$(GREETING)"
ports:
- containerPort: 8888
env:
- name: GREETING
valueFrom:
configMapKeyRef:
name: demo-config
key: greeting
利用 ConfigMap 创建配置文件
首先需要在 ConfigMap 中存储完整的配置文件(而不是一个个键),之后使用 ConfigMap 创建一个卷,最后将这个卷挂载到容器。
configmap.yaml
注意: YAML 中的 |
表示后面是原始数据块。
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-config
data:
config: |
greeting: Buongiorno
这里其内容为:
greeting: Buongiorno
部署模板的内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
spec:
replicas: 1
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- name: demo
image: cloudnatived/demo:hello-config-file
ports:
- containerPort: 8888
volumeMounts:
- mountPath: /config/
name: demo-config-volume
readOnly: true
volumes:
- name: demo-config-volume
configMap:
name: demo-config
items:
- key: config
path: demo.yaml
- 在 volumes 中利用配置文件创建了一个名为 demo-config-volume 的卷,文件名为 demo.yaml。
- 在 containers.volumeMounts 中将这个卷挂载到容器的 /config/ 路径。
- 最后将在容器中创建一个 /config/demo.yaml 文件。
查看集群中的 ConfigMap 数据:
PS C:\k8s> kubectl describe configmap/demo-config
Name: demo-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
config:
----
greeting: Buongiorno
BinaryData
====
Events: <none>
如果更新 ConfigMap 并修改它的值,则相应的文件也会自动更新。
有些应用程序会自动监测配置文件的更新,有些则不会。
可通过重新部署应用程序来读取修改。
配置发生变化后更新 Pod
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
name: demo
JiaJia:
书中说放在注释中,可以通过 helm 发布来触发自动重新部署,通过测试发现,更新后 checksum/config 注释的值确实变了,但是并没有触发重新部署。
powershellhelm install demo-config .\hello-config-file\ helm upgrade demo-config .\hello-config-file\
powershellkubectl port-forward deploy/demo 9999:8888
ConfigMap 更新前后的 deployment 的注解如下:
yamlmetadata: annotations: checksum/config: 16c967a4e6e489350734c14176d0db0db7fca24b38dcc3c5277d2a0d184cdb5c
yamlmetadata: annotations: checksum/config: fc80ec715dcecbce099238327a24c33c46cdf46e3f3dbc17b7aea7b14ca03caf
但 Pod 并没有更新。
将 ConfigMap 文件的 sha256 值移到环境变量中后,可以正常的触发 Pod 的更新。
yamlspec: ... template: ... spec: containers: - name: demo ... env: - name: checksum.config value: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
10.2 Kubernetes Secret
Kubernetes 提供了一种专门存储机密数据的特殊对象: Secret 。其清单示例如下:
apiVersion: v1
kind: Secret
metadata:
name: demo-secret
stringData:
magicWord: xyzzy
利用机密数据设置环境变量
部署文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
spec:
replicas: 1
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- name: demo
image: cloudnatived/demo:hello-secret-env
ports:
- containerPort: 8888
env:
- name: MAGIC_WORD
valueFrom:
secretKeyRef:
name: demo-secret
key: magicWord
配置 env.valueFrom.secretKeyRef 来从 Secret 中读取数据到环境变量(和 configMapKeyRef 类似)。
这里环境变量为 MAGIC_WORD ,其值来自 Secret 文件( demo-secret )中的 magicWord 。
镜像 cloudnatived/demo:hello-secret-env 中的代码如下(Go 语言):
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
magicWord := os.Getenv("MAGIC_WORD")
fmt.Fprintf(w, "The magic word is %q", magicWord)
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8888", nil))
}
将 MAGIC_WORD 环境变量的值读取到 magicWord 变量(magicWord := os.Getenv("MAGIC_WORD")
)并输出到响应。
应用部署文件:
PS C:\projects\github\cloudnativedevops\demo> kubectl apply -f .\hello-secret-env\k8s\
deployment.apps/demo created
secret/demo-secret created
将本地端口 9999 转发到 demo 部署的 8888 端口。
PS C:\projects\github\cloudnativedevops\demo> kubectl port-forward deploy/demo 9999:8888
Forwarding from 127.0.0.1:9999 -> 8888
Forwarding from [::1]:9999 -> 8888
Handling connection for 9999
访问 http://localhost:9999/ 地址:
GET http://localhost:9999/
---
HTTP/1.1 200 OK
Date: Mon, 16 May 2022 08:59:50 GMT
Content-Length: 25
Content-Type: text/plain; charset=utf-8
Connection: close
The magic word is "xyzzy"
之后可以通过如下方式清除资源:
PS C:\projects\github\cloudnativedevops\demo> kubectl delete -f .\hello-secret-env\k8s\
deployment.apps "demo" deleted
secret "demo-secret" deleted
将 Secret 写入文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
spec:
replicas: 1
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- name: demo
image: cloudnatived/demo:hello-secret-file
ports:
- containerPort: 8888
volumeMounts:
- name: demo-secret-volume
mountPath: "/secrets/"
readOnly: true
volumes:
- name: demo-secret-volume
secret:
secretName: demo-secret
镜像 cloudnatived/demo:hello-secret-file 对应的代码(Go 语言):
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
const secretPath = "/secrets/magicWord"
var magicWord []byte
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "The magic word is %q", magicWord)
}
func main() {
var err error
magicWord, err = ioutil.ReadFile(secretPath)
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8888", nil))
}
可以看到是从 /secrets/magicWord 文件中读取文件内容到变量 magicWord (magicWord, err = ioutil.ReadFile(secretPath)
)并将其打印到响应。而 magicWord 正是上面 Secret 文件中键值对的键的名称,读取到的内容是键对应的值。
实际操作及控制台的输出内容如下:
应用部署:
PS C:\projects\github\cloudnativedevops\demo> kubectl apply -f .\hello-secret-file\k8s\
Warning: resource deployments/demo is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
deployment.apps/demo configured
secret/demo-secret created
将本地端口 9999 转发到 demo 部署的 8888 端口。
PS C:\projects\github\cloudnativedevops\demo> kubectl port-forward deploy/demo 9999:8888
Forwarding from 127.0.0.1:9999 -> 8888
Forwarding from [::1]:9999 -> 8888
访问 http://localhost:9999/ 地址:
GET http://localhost:9999/
---
HTTP/1.1 200 OK
Date: Mon, 16 May 2022 08:00:43 GMT
Content-Length: 25
Content-Type: text/plain; charset=utf-8
Connection: close
The magic word is "xyzzy"
之后可以通过如下方式清除资源:
PS C:\projects\github\cloudnativedevops\demo> kubectl delete -f .\hello-secret-file\k8s\
deployment.apps "demo" deleted
secret "demo-secret" deleted
读取 Secret
通过 kubectl describe
命令查看 Secret 内容:
PS C:\k8s> kubectl describe secret/demo-secret
Name: demo-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
magicWord: 5 bytes
这次没有显示实际数据。Kubernetes Secret 的类型是 Opaque ,这意味着它们不会在 kubectl describe
的输出、日志消息或终端中显示。这样可以防止意外泄漏机密数据。
使用 kubectl get
查看混淆后的机密数据:
PS C:\k8s> kubectl get secret/demo-secret -o yaml
apiVersion: v1
data:
magicWord: eHl6enk=
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"demo-secret","namespace":"default"},"stringData":{"magicWord":"xyzzy"}}
creationTimestamp: "2022-05-16T09:09:11Z"
name: demo-secret
namespace: default
resourceVersion: "1750142"
uid: dd8ad1f8-6a76-4da0-83a5-50762a97cdca
type: Opaque
由于机密数据可以是不可打印的二进制数据(比如 TLS 加密密钥),因此 Kubernetes 的 Secret 始终以 base64 格式存储。
解码 base64 数据:
echo "eHl6enk=" | base64 --decode
JiaJia:
PowerShell 下调用如下函数解码:
powershellPS C:\k8s> [System.Text.Encoding]::UTF8.GetString([System.Text.Encoding]::UTF8.GetBytes("eHl6enk=")) eHl6enk=
也可以将其包装成如下函数,然后再调用:
powershellfunction ConvertFrom-Base64($string) { $bytes = [System.Convert]::FromBase64String($string); $decoded = [System.Text.Encoding]::UTF8.GetString($bytes); return $decoded; }
运行效果:
powershellPS C:\k8s> function ConvertFrom-Base64($string) { >> $bytes = [System.Convert]::FromBase64String($string); >> $decoded = [System.Text.Encoding]::UTF8.GetString($bytes); >> return $decoded; >> } PS C:\k8s> ConvertFrom-Base64("eHl6enk=") xyzzy
也可以使用 base64 命令对文本进行编码:
echo -n xyzzy | base64
JiaJia:
PowerShell 可以使用如下函数将文本转化为 base64 格式:
powershellfunction ConvertTo-Base64($string) { $bytes = [System.Text.Encoding]::UTF8.GetBytes($string); $encoded = [System.Convert]::ToBase64String($bytes); return $encoded; }
运行效果:
powershellPS C:\k8s> function ConvertTo-Base64($string) { >> $bytes = [System.Text.Encoding]::UTF8.GetBytes($string); >> $encoded = [System.Convert]::ToBase64String($bytes); >> return $encoded; >> } PS C:\k8s> ConvertTo-Base64("xyzzy") eHl6enk=
访问 Secret
随可以访问或编辑 Secret 是由 Kubernetes 访问控制机制 RBAC 控制的,将在 11.1 节中详细讨论 RBAC。
如果你使用的集群不支持 RBAC 或 未启用 RBAC,则所有用户或任何容器都可以访问所有 Secret(千万不要在没有 RBAC 的情况下在生产环境中运行任何集群)。
静态加密
Kubernetes 从 1.7 版开始支持静态加密。这意味着 etcd 数据库中的机密数据实际上是经过加密后存储在磁盘上的,即使可以直接访问数据库的人也无法读取。这样 Kubernetes API 服务器拥有加密此数据的密钥。正确配置的集群应当启用静态加密。
kubectl describe pod -n kube-system -l component=kube-apiserver | grep encryption
JiaJia:
PowerShell 中将 grep 改为 findstr
powershellPS C:\k8s> kubectl describe pod -n kube-system -l component=kube-apiserver | findstr encryption
如果没有看到 experimental-encryption-provider-config 标志,则表明密钥启用静态加密。
如果你使用的是 GKE 或其它托管的 Kubernetes 服务,则可以放心,你的数据已经通过其它机制进行了加密,因此看不到这个标志。
防止 Secret 被删
可以使用 Helm 专用的注解,防止资源被删除。
apiVersion: v1
kind: Secret
metadata:
annotations:
"helm.sh/resource-policy": keep
name: demo-secret
stringData:
magicWord: xyzzy
10.3 Secret 管理策略
上一节的清单文件中的数据是明文保存的。永远不要在源代码管理的文件中公开这样的机密数据。
在选择工具或策略来管理应用程序中的机密信息时,你需要考虑以下几个该问题:
- 将机密存储在何处才能保证高可用性?
- 运行中的应用程序应当如何使用机密?
- 在轮换或改变机密时,运行中的应用程序需要做些什么?
在版本控制中加密机密
管理机密的第一种方式是将机密数据直接存储到版本控制代码库的代码中,切记以加密形式存储,并在部署时进行解密。
你应该严格限制加密密钥的访问,只允许某些特定的人访问,而且绝不能公开给开发人员。
缺点是同样的机密可能会存在于多个项目中,修改的工作量会比较大。
对于只有非关键数据的小型组织而言,在源代码库中保存加密的机密数据是一个不错的起点。
远程存储 Secret
另一种管理机密的方法是将它们保存到一个(或多个)文件中,然后把文件保存在异地的安全文件存储库中,例如 AWS S3 存储桶或 Google 云存储。在部署应用时,下载这些文件,经过解密后提供给应用程序。
跟第一种方式比较类似,但解决了同一个机密在多个代码库中重复存储的问题。
使用专业的机密管理工具
当规模非常大时,可能需要考虑使用专业的机密管理工具,例如 Hashicorp Vault、Square Keywhiz、AWS Secrets Manager 或 Azure Key Vault 。
这些工具可以将所有应用程序的机密数据集中存储在安全的地方,不仅可以提供高可用性,而且还可以控制哪些用户和服务账号有权添加、删除、更改或查看机密数据。
上述工具中最受欢迎的是 Hashicorp Vault 。
推荐
建议使用 Sops 之类的轻量级加密工具,直接在源代码中对机密数据进行加密。因为一般你没有那么多机密需要管理。除非你的基础设施非常复杂且相互依赖。
10.4 使用 Sops 加密机密数据
Mozilla 项目开发的 Sops ( secrets operations 的缩写 )是一种加密/解密工具,能够处理 YAML、JSON 和二进制文件,而且还支持多个加密后端,包括 PGP/GnuPG、Azure Key Vault、AWS 的密钥管理服务(KMS)以及 Google 的云密钥管理服务。
Sops 简介
Sops 不会加密整个文件,它只加密各个机密数据的值。
有关 Sops 的安装请参照项目主页:https://github.com/mozilla/sops 。
使用 Sops 加密文件
Sops 本身并不会处理加密。它将加密的工作委托给后端,比如 GnuPG(Pretty Good Privacy,即 PGP 协议的一种流行的开源实现)。
PGP 加密与 SSH 和 TLS 一样,是一个公钥加密系统。
首先,使用 gpg --gen-key
生成一对密钥。中间需要输入 real name 、 email address 和密码(密码后面解密时会使用到)。
gpg --gen-key
JiaJia:
powershellPS C:\k8s> gpg --gen-key gpg (GnuPG) 2.3.6; Copyright (C) 2021 g10 Code GmbH This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Note: Use "gpg --full-generate-key" for a full featured key generation dialog. GnuPG needs to construct a user ID to identify your key. Real name: JiaJia Email address: jiajia@gmail.com You selected this USER-ID: "JiaJia <jiajia@gmail.com>" Change (N)ame, (E)mail, or (O)kay/(Q)uit? O We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy. We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy. gpg: directory 'C:\\Users\\ljj\\AppData\\Roaming\\gnupg\\openpgp-revocs.d' created gpg: revocation certificate stored as 'C:\\Users\\ljj\\AppData\\Roaming\\gnupg\\openpgp-revocs.d\\IHT6OR9022D5WUE5SC8D4498GNP7L7MOJ7PH3Z0M.rev' public and secret key created and signed. pub ed25519 2022-05-17 [SC] [expires: 2024-05-16] IHT6OR9022D5WUE5SC8D4498GNP7L7MOJ7PH3Z0M uid JiaJia <jiajia@gmail.com> sub cv25519 2022-05-17 [E] [expires: 2024-05-16]
其中 IHT6OR9022D5WUE5SC8D4498GNP7L7MOJ7PH3Z0M 就是密钥指纹,后面加密时会使用到。
同时还会生成一个对应的撤销证书 IHT6OR9022D5WUE5SC8D4498GNP7L7MOJ7PH3Z0M.rev 。其内容如下:
txtThis is a revocation certificate for the OpenPGP key: pub ed25519 2022-05-17 [SC] [expires: 2024-05-16] IHT6OR9022D5WUE5SC8D4498GNP7L7MOJ7PH3Z0M uid JiaJia <jiajia@gmail.com> A revocation certificate is a kind of "kill switch" to publicly declare that a key shall not anymore be used. It is not possible to retract such a revocation certificate once it has been published. Use it to revoke this key in case of a compromise or loss of the secret key. However, if the secret key is still accessible, it is better to generate a new revocation certificate and give a reason for the revocation. For details see the description of of the gpg command "--generate-revocation" in the GnuPG manual. To avoid an accidental use of this file, a colon has been inserted before the 5 dashes below. Remove this colon with a text editor before importing and publishing this revocation certificate. :-----BEGIN PGP PUBLIC KEY BLOCK----- Comment: This is a revocation certificate iHgEIBYKACAWiQTvMEp0xEU1an6MQIpAGnJA7Je1aAUCYoM9bQIdBBBKCRBAGnJA 7Je1aLV+AQC8v236150pD51Ln0be0WLaGDzOdHgaylK1KdX+SD3PDQEAmwb/BUOm fcCMAq2MgNyjTa/qf3epBbmft3gxhgoEcQo= =4LG3 -----END PGP PUBLIC KEY BLOCK-----
安装 Sops
可以从项目仓库 https://github.com/mozilla/sops 下载,也可以通过 Go 来安装:
go get -u go.mozilla.org/sops/cmd/sops
sops -v
JiaJia:
我这里使用 Go 安装没成功,直接从 项目仓库 下载的 sops-v3.7.3.exe 文件,将其改名为 sops.exe ,然后将目录加入 Path 环境变量(需要重启命令行控制台)。
下载不下来的可以使用我共享的文件:【金山文档】sops-v3.7.3。
powershellPS C:\k8s> sops -v sops 3.7.3 [warning] failed to retrieve latest version from upstream: Get "https://raw.githubusercontent.com/mozilla/sops/master/version/version.go": dial tcp: lookup raw.githubusercontent.com: getaddrinfow: The requested name is valid, but no data of the requested type was found. [warning] failed to compare current version with latest: Version string empty (latest)
对机密文件进行加密
加密前( secret.yaml ):
apiVersion: v1
kind: Secret
metadata:
name: demo-secret
stringData:
magicWord: xyzzy
对清单文件进行加密:
sops --encrypt --in-place --pgp IHT6OR9022D5WUE5SC8D4498GNP7L7MOJ7PH3Z0M .\secret.yaml
JiaJia:
powershellPS C:\k8s> sops --encrypt --in-place --pgp IHT6OR9022D5WUE5SC8D4498GNP7L7MOJ7PH3Z0M .\secret.yaml [PGP] time="2022-05-17T14:30:48+08:00" level=warning msg="Deprecation Warning: GPG key fetching from a keyserver within sops will be removed in a future version of sops. See https://github.com/mozilla/sops/issues/727 for more information."
执行时有个弃用警告,貌似以后将移除从密钥服务器获取密钥的功能,不知道支不支持在加密时指定密钥文件。
但是,暂时好像也没有提供直接从文件读取密钥的功能,而且在之前通过gpg --gen-key
生成密钥时也没看到哪个是公钥/私钥。
Windows 版安装时会附带安装一个 Kleopatra 软件,这里会列出已经创建的密钥,也可以通过这个软件里的菜单创建密钥、导出密钥、吊销认证、更改到期日期。
导出的文件为 .asc 后缀,可以通过gpg --import filename.asc
命令来导入密钥。加密后文件:
yamlapiVersion: ENC[AES256_GCM,data:kw0=,iv:ODCJHP7UfhcOIylfenNiI2PGBbUQPDfKrfmvJF3ZJNQ=,tag:2OfdVDlizaPFuyBohqw8gA==,type:str] kind: ENC[AES256_GCM,data:h+JSJpi2,iv:gbzeMdyBhxOeHfq2k90k1QAXKbM0Y8IBGokF7AUX668=,tag:cX2xWAiV9yqImA+mvEICVQ==,type:str] metadata: name: ENC[AES256_GCM,data:tX8Tvtf74ZL4EE4=,iv:OltIC2JO48M8OxUweNkhsyfpFVO3SwyndU2B9LrdQy4=,tag:01WfuucFxJQPrtNG6WYYfQ==,type:str] stringData: magicWord: ENC[AES256_GCM,data:cCupDhg=,iv:rUDkh51WFa+Jqu5i7QJbPgsVdC5wuqr83Krmm6P6hzU=,tag:MbTIKdIyn9UdwT5yCdlxfg==,type:str] sops: kms: [] gcp_kms: [] azure_kv: [] hc_vault: [] age: [] lastmodified: "2022-05-17T06:30:50Z" mac: ENC[AES256_GCM,data:EgcWojzhy9LIMUlmamIlbDzh2wLyBYzhw7vG8Yuwj6+IpfHDPhH5XeREGdSP2xDuQDYggZ0jo+SZsgGmOAD6iW2fR/9hKP/XRWqbkDvRhTQEV162iQSwGG1F4fq4OErScZsEetFTGSi3z6Ly2ek2vMjSJnrHCn8QRPv+gg9eE1s=,iv:askdsJ2DFI2prvEiiPBlGVd3Gfk18ZJbmtVDtidnBbE=,tag:VQ10Vl7MmLFd7e4YalI5dQ==,type:str] pgp: - created_at: "2022-05-17T06:30:47Z" enc: "-----BEGIN PGP MESSAGE-----\r\n\r\nhF4DKbfoNF76ZNwSAQdAkAJ8kPHs9fS+Gw4Llp00Kh7iYuJ4hd4g5KB5g0REFjEw\r\nC0o5syW5J29Gw4YeyUgZUo3Iq5tJk5y9IGAjNA3ADRQN8e8G2wObpfboaiyU/zt7\r\n1GYBCQIQjP8BjGfbdCJf2jvaIiRQ8W1GCWzmDiQcAuz2oc6CYS94bs4APo9z1KqD\r\naWiLGjuSSY5AC+gW/JCfNOGZY9v+C+4MgjF2plBbPKxMG0EB/1qBUfp6QZMZEzV2\r\ng8TsYpm4qR0=\r\n=CsXe\r\n-----END PGP MESSAGE-----\r\n" fp: IHT6OR9022D5WUE5SC8D4498GNP7L7MOJ7PH3Z0M unencrypted_suffix: _unencrypted version: 3.7.3
解密文件:
sops --decrypt .\secret.yaml
输入命令后会有弹窗要求输入创建密钥时输入的密码。解密成功后只会将解密的结果打印在控制台,不会修改源文件。
JiaJia:
powershellPS C:\k8s> sops --decrypt .\secret.yaml apiVersion: v1 kind: Secret metadata: name: demo-secret stringData: magicWord: xyzzy
可以通过添加
--in-place
标志,直接将解密结果写入原文件。powershellsops --decrypt --in-place .\secret.yaml
使用 KMS 后端
如果你使用 Amazon KMS 或 Google Cloud KMS 在云中管理密钥,则可以结合 Sops 一起使用。使用 KMS 密钥的方式与 PGP 示例完全相同,只不过文件中的元数据会有所不同。
10.5 小结
- 分离配置数据与应用程序代码,然后使用 Kubernetes ConfigMap 和 Secret 进行部署。这样就无需在每次更改密码时重新部署应用程序。
- 为了将数据放入 ConfigMap,你可以直接写入 Kubernetes 清单文件,或使用 kubectl 将现有的 YAML 文件转换为 ConfigMap 规范。
- 在数据进入 ConfigMap 之后,就可以将其插入到容器的环境中,或添加到入口点命令行的参数中。或者,你也可以将数据写入挂载到容器的文件中。
- Secret 的工作方式与 ConfigMap 相同,除了数据是静态加密的, kubectl 的输出会显示混淆的数据。
- 一种简单又灵活的管理机密数据的方法是将机密数据直接存储在源代码库中,但一定要使用 Sops 或其它文本加密工具对其进行加密。
- 不要过度考虑机密管理,尤其是刚开始的时候。从简单且方便开发人员设置的方式入手。
- 如果许多应用程序共享机密,则可以将它们(加密后)存储在云存储桶中,并在部署时再获取。
- 企业级的机密管理需要专业的服务,例如 Vault。但不要优先考虑 Vault,因为你可能并不需要。而且,你可以随时切换到 Vault。
- Sops 是一种加密工具,可以处理 YAML 和 JSON 等键值文件。它可以从本地 GnuPG 密钥环或云密钥管理服务(比如 Amazon KMS 和 Google Cloud KMS)获取加密密钥。