<!-- 《基于 Kubernetes 的云原生 DevOps》第10章 配置和机密数据 --> <!-- cloud-native-devops-with-kubernetes-ch-10 --> --- > If you want to keep a secret, you must also hide it from yourself. > > -- George Orwell, 1984 --- [TOC] Kubernetes 提供了几种不同的方法来帮助管理配置: 1. 通过 *Pod* 规范中的环境变量将值传递给应用程序; 2. 使用 *ConfigMap* 和 *Secret* 对象将配置数据直接存储在 Kubernetes 中; ## 10.1 ConfigMap *ConfigMap* 是 Kubernetes 中存储配置数据的主要对象。你可以视其为**存储配置数据的一组命名键值对**。 **创建 ConfigMap** 假设你需要在 Pod 的文件系统中创建一个名为 *config.yaml* 的 YAML 配置文件: ```yaml autoSaveInterval: 60 batchSize: 18 protocols: - http - https ``` 可以直接将文件中的内容原封不动的嵌入到 *ConfigMap* 配置中, 书中示例的格式如下: ```yaml apiVersion: v1 data: config.yaml: | autoSaveInterval: 60 batchSize: 18 protocols: - http - https kind: ConfigMap metadata: name: demo-config namespace: demo ``` **注意:**YAML 中的 `|` 表示后面是原始数据块。 > **JiaJia:** > > 现在项目使用的是阿里云,其 *ConfigMap* 格式如下: > > ```yaml > apiVersion: v1 > data: > autoSaveInterval: '60' > batchSize: '18' > kind: ConfigMap > metadata: > name: demo-config > namespace: demo > ``` > > 纯数字的话需要用单引号括起来,数组方式貌似不支持。 还有一种更简单的方法,可以通过 *kubectl* 利用 YAML 文件创建 *ConfigMap* 。 ```powershell PS C:\k8s> kubectl create configmap demo-config --namespace=demo --from-file=config.yaml configmap/demo-config created ``` 创建的配置文件内容如下: ```yaml 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* 对应的清单文件: ```powershell kubectl get configmap/demo-config --namespace=demo -o yaml > demo-config.yaml ``` 导出文件 *demo-config.yaml* 内容如下: ```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:* ```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* 中配置的键值对。 ```yaml apiVersion: v1 kind: ConfigMap metadata: name: demo-config data: greeting: Hola ``` *deployment.yaml:* 在部署的规格中,通过 *env* 指定环境变量,这里的区别是用 *valueFrom* 替代了 *value*,然后通过 *configMapKeyRef* 中的配置标识环境变量的值来自哪个 *ConfigMap* 中的哪个 *key* 。 ```yaml 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 ``` ```powershell kubectl apply -f .\hello-config-env\k8s\ configmap/demo-config created deployment.apps/demo created ``` ```powershell kubectl port-forward deploy/demo 9999:8888 ``` ```http 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* 。 ```yaml spec: containers: - name: demo image: cloudnatived/demo:hello-config-env ports: - containerPort: 8888 envFrom: - configMapRef: name: demo-config ``` 使用 *envFrom* 的同时仍然可以使用 *env* 指定环境变量,并且同名时 *env* 中的优先级更高。 **在命令参数中指定环境变量** 可以使用 Kubernetes 的特殊语法 `$(VARIABLE)` 在命令行(容器入口)中引用环境变量。 *args* 中指定的参数会传送到容器的默认入口点。 ```yaml 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 中的 `|` 表示后面是原始数据块。 ```yaml apiVersion: v1 kind: ConfigMap metadata: name: demo-config data: config: | greeting: Buongiorno ``` 这里其内容为: ```yaml greeting: Buongiorno ``` 部署模板的内容如下: ```yaml 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 数据: ```yaml 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** ```yaml apiVersion: apps/v1 kind: Deployment metadata: annotations: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} name: demo ``` > **JiaJia:** > > 书中说放在注释中,可以通过 *helm* 发布来触发自动重新部署,通过测试发现,更新后 *checksum/config* 注释的值确实变了,但是并没有触发重新部署。 > > ```powershell > helm install demo-config .\hello-config-file\ > helm upgrade demo-config .\hello-config-file\ > ``` > > ```powershell > kubectl port-forward deploy/demo 9999:8888 > ``` > > *ConfigMap* 更新前后的 *deployment* 的注解如下: > > ```yaml > metadata: > annotations: > checksum/config: 16c967a4e6e489350734c14176d0db0db7fca24b38dcc3c5277d2a0d184cdb5c > ``` > > ```yaml > metadata: > annotations: > checksum/config: fc80ec715dcecbce099238327a24c33c46cdf46e3f3dbc17b7aea7b14ca03caf > ``` > > 但 Pod 并没有更新。 > > **将 ConfigMap 文件的 *sha256* 值移到环境变量中后,可以正常的触发 Pod 的更新。** > > ```yaml > spec: > ... > template: > ... > spec: > containers: > - name: demo > ... > env: > - name: checksum.config > value: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} > ``` ## 10.2 Kubernetes Secret Kubernetes 提供了一种专门存储机密数据的特殊对象: *Secret* 。其清单示例如下: ```yaml apiVersion: v1 kind: Secret metadata: name: demo-secret stringData: magicWord: xyzzy ``` **利用机密数据设置环境变量** 部署文件: ```yaml 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 语言): ```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")`)并输出到响应。 应用部署文件: ```powershell PS C:\projects\github\cloudnativedevops\demo> kubectl apply -f .\hello-secret-env\k8s\ deployment.apps/demo created secret/demo-secret created ``` 将本地端口 9999 转发到 *demo* 部署的 8888 端口。 ```powershell 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/ 地址: ```http 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" ``` 之后可以通过如下方式清除资源: ```powershell PS C:\projects\github\cloudnativedevops\demo> kubectl delete -f .\hello-secret-env\k8s\ deployment.apps "demo" deleted secret "demo-secret" deleted ``` **将 Secret 写入文件** ```yaml 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 语言): ```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* 文件中键值对的键的名称,读取到的内容是键对应的值。 实际操作及控制台的输出内容如下: 应用部署: ```powershell 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 端口。 ```powershell 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/ 地址: ```http 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" ``` 之后可以通过如下方式清除资源: ```powershell 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 内容: ```powershell 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` 查看混淆后的机密数据: ```powershell 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* 数据: ```bash echo "eHl6enk=" | base64 --decode ``` > **JiaJia:** > > PowerShell 下调用如下函数解码: > > ```powershell > PS C:\k8s> [System.Text.Encoding]::UTF8.GetString([System.Text.Encoding]::UTF8.GetBytes("eHl6enk=")) > eHl6enk= > ``` > > 也可以将其包装成如下函数,然后再调用: > > ```powershell > function ConvertFrom-Base64($string) { > $bytes = [System.Convert]::FromBase64String($string); > $decoded = [System.Text.Encoding]::UTF8.GetString($bytes); > return $decoded; > } > ``` > > 运行效果: > > ```powershell > PS 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* 命令对文本进行编码: ```bash echo -n xyzzy | base64 ``` > **JiaJia:** > > PowerShell 可以使用如下函数将文本转化为 *base64* 格式: > > ```powershell > function ConvertTo-Base64($string) { > $bytes = [System.Text.Encoding]::UTF8.GetBytes($string); > $encoded = [System.Convert]::ToBase64String($bytes); > return $encoded; > } > ``` > > 运行效果: > > ```powershell > PS 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 服务器拥有加密此数据的密钥。正确配置的集群应当启用静态加密。 ```bash kubectl describe pod -n kube-system -l component=kube-apiserver | grep encryption ``` > **JiaJia:** > > PowerShell 中将 *grep* 改为 *findstr* > > ```powershell > PS C:\k8s> kubectl describe pod -n kube-system -l component=kube-apiserver | findstr encryption > ``` 如果没有看到 *experimental-encryption-provider-config* 标志,则表明密钥启用静态加密。 如果你使用的是 GKE 或其它托管的 Kubernetes 服务,则可以放心,你的数据已经通过其它机制进行了加密,因此看不到这个标志。 **防止 Secret 被删** 可以使用 Helm 专用的注解,防止资源被删除。 ```yaml apiVersion: v1 kind: Secret metadata: annotations: "helm.sh/resource-policy": keep name: demo-secret stringData: magicWord: xyzzy ``` ## 10.3 Secret 管理策略 上一节的清单文件中的数据是明文保存的。**永远不要在源代码管理的文件中公开这样的机密数据。** 在选择工具或策略来管理应用程序中的机密信息时,你需要考虑以下几个该问题: 1. 将机密存储在何处才能保证高可用性? 2. 运行中的应用程序应当如何使用机密? 3. 在轮换或改变机密时,运行中的应用程序需要做些什么? **在版本控制中加密机密** 管理机密的第一种方式是将机密数据直接存储到版本控制代码库的代码中,切记以加密形式存储,并在部署时进行解密。 你应该严格限制加密密钥的访问,只允许某些特定的人访问,而且绝不能公开给开发人员。 缺点是同样的机密可能会存在于多个项目中,修改的工作量会比较大。 对于只有非关键数据的小型组织而言,在源代码库中保存加密的机密数据是一个不错的起点。 **远程存储 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](https://github.com/mozilla/sops) 。 **使用 Sops 加密文件** Sops 本身并不会处理加密。它将加密的工作委托给后端,比如 **GnuPG**(*Pretty Good Privacy*,即 **PGP** 协议的一种流行的开源实现)。 PGP 加密与 SSH 和 TLS 一样,是一个公钥加密系统。 首先,使用 `gpg --gen-key` 生成一对密钥。中间需要输入 *real name* 、 *email address* 和密码(密码后面解密时会使用到)。 ```bash gpg --gen-key ``` > **JiaJia:** > > ```powershell > PS 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* 。其内容如下: > > ```pgp > This 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](https://github.com/mozilla/sops) 下载,也可以通过 Go 来安装: ```shell go get -u go.mozilla.org/sops/cmd/sops sops -v ``` > **JiaJia:** > > 我这里使用 Go 安装没成功,直接从 [项目仓库](https://github.com/mozilla/sops/releases) 下载的 *sops-v3.7.3.exe* 文件,将其改名为 *sops.exe* ,然后将目录加入 *Path* 环境变量(需要重启命令行控制台)。 > > 下载不下来的可以使用我共享的文件:[【金山文档】 sops-v3.7.3](https://kdocs.cn/l/cve9qNDoZQyN)。 > > ```powershell > PS 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* ): ```yaml apiVersion: v1 kind: Secret metadata: name: demo-secret stringData: magicWord: xyzzy ``` 对清单文件进行加密: ```powershell sops --encrypt --in-place --pgp IHT6OR9022D5WUE5SC8D4498GNP7L7MOJ7PH3Z0M .\secret.yaml ``` > **JiaJia:** > > ```powershell > PS 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` 命令来导入密钥。 > > 加密后文件: > > ```yaml > apiVersion: 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 > ``` 解密文件: ```powershell sops --decrypt .\secret.yaml ``` 输入命令后会有弹窗要求输入创建密钥时输入的密码。解密成功后只会将解密的结果打印在控制台,不会修改源文件。 > **JiaJia:** > > ```powershell > PS C:\k8s> sops --decrypt .\secret.yaml > apiVersion: v1 > kind: Secret > metadata: > name: demo-secret > stringData: > magicWord: xyzzy > ``` > > 可以通过添加 `--in-place` 标志,直接将解密结果写入原文件。 > > ```powershell > sops --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)获取加密密钥。 Loading... 版权声明:本文为博主「佳佳」的原创文章,遵循 CC 4.0 BY-NC-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://www.liujiajia.me/2022/5/17/cloud-native-devops-with-kubernetes-ch-10 提交