使用 Secret 配置应用程序

KubernetesBeginner
立即练习

介绍

应用程序通常需要一些不应直接放入容器镜像或明文清单(manifest)中的值,例如密码、令牌或数据库凭据。Kubernetes Secret 将这类敏感配置存储为独立的对象,并允许 Pod 在启动时使用它们。

在本实验中,你将创建一个 Secret,仔细检查其编码数据,将其连接到应用程序 Pod,更新 Secret,并重新创建 Pod 以便应用程序读取新值。本实验重点介绍 Secret 的核心工作流程。后续实验将练习更详细的环境变量注入和基于文件的配置挂载。

准备 Secret 练习命名空间

在此步骤中,你将准备一个用于 Secret 练习的干净命名空间。

从 /home/labex/project 开始。你将在此目录中编写一个 Pod 清单。

mkdir -p /home/labex/project
cd /home/labex/project

检查 Kubernetes API 服务器是否可达。如果虚拟机刚启动且节点尚未就绪,请等待几秒钟后再运行该命令。

kubectl get nodes --request-timeout=5s

预期输出包括:

NAME         STATUS   ROLES           AGE   VERSION
labex-v135   Ready    control-plane   ...   v1.35...

在创建应用程序对象之前,请等待集群网络 Pod 就绪。

kubectl wait --namespace kube-system --for=condition=Ready pod -l k8s-app=calico-node --timeout=120s

预期输出:

pod/calico-node-... condition met

创建一个名为 ckad-prep-secrets 的命名空间:

kubectl create namespace ckad-prep-secrets

将此命名空间设置为当前上下文的命名空间:

kubectl config set-context --current --namespace=ckad-prep-secrets

确认当前命名空间:

kubectl config view --minify --output 'jsonpath={..namespace}'; echo

预期输出:

ckad-prep-secrets

创建并检查 Secret

在此步骤中,你将创建一个存储小型应用程序数据库连接设置的 Secret。

kubectl create secret generic 命令用于创建一个不透明(Opaque)的 Secret。generic 意味着 Kubernetes 不会为这些键分配特殊的内置含义。每个 --from-literal 标志都会向 Secret 添加一个键值对。

kubectl create secret generic db-credentials \
  --from-literal=DB_USER=orders-app \
  --from-literal=DB_PASSWORD=dev-password-123 \
  --from-literal=DB_NAME=orders

预期输出:

secret/db-credentials created

检查 Secret 摘要:

kubectl get secret db-credentials

预期输出:

NAME             TYPE     DATA   AGE
db-credentials   Opaque   3      ...

以 YAML 格式查看 Secret。Kubernetes 将 data 下的值存储为 base64 编码的字符串,因此你不应期望在此输出中看到原始密码。

kubectl get secret db-credentials -o yaml

预期输出包括:

data:
  DB_NAME: b3JkZXJz
  DB_PASSWORD: ...
  DB_USER: b3JkZXJzLWFwcA==
type: Opaque

Base64 编码不是加密。任何能够读取 Secret 对象的人都可以解码其值。此处仅解码用户名,以便在不打印密码的情况下了解其表示方式。

kubectl get secret db-credentials -o 'jsonpath={.data.DB_USER}' | base64 --decode; echo

预期输出:

orders-app

通过 Secret 配置 Pod

在此步骤中,你将启动一个将 Secret 读取为环境变量的 Pod。

envFrom.secretRef 字段将 Secret 中的所有键导入到容器环境中。这类似于上一个实验中的 ConfigMap 模式,但值来自 Secret 对象。此 Pod 中的命令会打印用户名和数据库名称,然后仅确认密码已加载。它不会打印密码本身。

创建 secret-reader-pod.yaml:

cat > secret-reader-pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: secret-reader
  labels:
    app: secret-reader
spec:
  terminationGracePeriodSeconds: 0
  containers:
  - name: reader
    image: busybox:1.36
    command:
    - /bin/sh
    - -c
    - 'echo user=${DB_USER}; echo database=${DB_NAME}; if [ -n "${DB_PASSWORD}" ]; then echo password-loaded; fi; sleep 3600'
    envFrom:
    - secretRef:
        name: db-credentials
EOF

应用 Pod 清单:

kubectl apply -f secret-reader-pod.yaml

预期输出:

pod/secret-reader created

等待 Pod 就绪:

kubectl wait --for=condition=Ready pod/secret-reader --timeout=120s

预期输出:

pod/secret-reader condition met

读取 Pod 日志。应用程序确认 Secret 值可用,但未显示密码值。

kubectl logs secret-reader -c reader

预期输出:

user=orders-app
database=orders
password-loaded

更新 Secret 并重新创建 Pod

在此步骤中,你将更新 Secret 并重新创建 Pod,以便容器接收新值。

容器环境是在容器启动时捕获的。如果 Secret 是作为环境变量使用的,更新 Secret 对象不会重写已运行容器内的环境。当应用程序必须读取更新后的 Secret 值时,请启动一个新的 Pod。

使用新的应用程序用户和密码修补(patch)Secret:

kubectl patch secret db-credentials --type merge --patch '{"stringData":{"DB_USER":"orders-worker","DB_PASSWORD":"rotated-password-456","DB_NAME":"orders"}}'

预期输出:

secret/db-credentials patched

通过仅解码 DB_USER 来确认更新后的用户名:

kubectl get secret db-credentials -o 'jsonpath={.data.DB_USER}' | base64 --decode; echo

预期输出:

orders-worker

删除旧的 Pod:

kubectl delete pod secret-reader

预期输出包括:

pod "secret-reader" deleted

从相同的清单重新创建 Pod 并等待其就绪:

kubectl apply -f secret-reader-pod.yaml
kubectl wait --for=condition=Ready pod/secret-reader --timeout=120s

预期输出:

pod/secret-reader created
pod/secret-reader condition met

再次读取日志。新的 Pod 现在可以看到来自 Secret 的更新后的用户,并且仍然避免打印原始密码。

kubectl logs secret-reader -c reader

预期输出:

user=orders-worker
database=orders
password-loaded

总结

你创建了一个 Kubernetes Secret,检查了其编码数据,解码了一个非敏感键以了解其表示方式,将 Secret 连接到应用程序 Pod,并在更新 Secret 后重新创建了 Pod。你还练习了一个处理敏感配置的重要习惯:验证应用程序是否已接收到密码,同时不在日志中打印密码值。