首页
/ Kubernetes The Hard Way 冒烟测试:验证 Secret 静态加密、Deployment 与 NodePort 服务链路

Kubernetes The Hard Way 冒烟测试:验证 Secret 静态加密、Deployment 与 NodePort 服务链路

2026-09-05 17:04:43作者:宣利权Counsellor

Kubernetes The Hard Way 教程中,冒烟测试(Smoke Test)是完成全部控制面与节点引导后的最后一道功能验收环节。本篇以 docs/12-smoke-test.md 为主线,带你在一套单控制面节点(server)加两个工作节点(node-0node-1)的集群上,逐项验证四项关键能力:Secret 数据在 etcd 中的静态加密、Deployment 的创建与管理、端口转发/日志/容器内执行等调试手段,以及通过 NodePort Service 暴露应用。读完后,你不仅掌握这套验收命令,还能理解每个验证结果背后的组件配置来源,从而在测试失败时快速定位到具体配置项。

冒烟测试执行前的集群状态

进入本篇之前,你需要已经按教程顺序完成以下各阶段(详见 Labs 列表):

本教程使用的组件版本为 Kubernetes v1.32.x(downloads-amd64.txt 中锁定的是 v1.32.3)、etcd v3.6.x、containerd v2.1.x、CNI v1.6.x。所有命令都从 jumpbox 机器上执行,kubectl 已配置好指向 https://server.kubernetes.local:6443 的 admin 凭据。

验证一:Secret 数据静态加密

创建待验证的 Secret

冒烟测试的第一项是验证集群具备"静态加密 Secret 数据"的能力。先创建一个 generic secret:

kubectl create secret generic kubernetes-the-hard-way \
  --from-literal="mykey=mydata"

这条命令通过 API Server 将名为 kubernetes-the-hard-way 的 Secret 写入 default 命名空间。如果前几篇实验室配置正确,API Server 会在把数据落盘到 etcd 之前,先按加密配置对其进行加密。

在 etcd 中检查密文

SSH 登录控制面节点,用 etcdctl 取出该 Secret 的原始存储内容并做十六进制转储:

ssh root@server \
    'etcdctl get /registry/secrets/default/kubernetes-the-hard-way | hexdump -C'

预期输出形如:

00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
00000010  73 2f 64 65 66 61 75 6c  74 2f 6b 75 62 65 72 6e  |s/default/kubern|
00000020  65 74 65 73 2d 74 68 65  2d 68 61 72 64 2d 77 61  |etes-the-hard-wa|
00000030  79 0a 6b 38 73 3a 65 6e  63 3a 61 65 73 63 62 63  |y.k8s:enc:aescbc|
00000040  3a 76 31 3a 6b 65 79 31  3a 4f 1b 80 d8 89 72 f4  |:v1:key1:O....r.|
00000050  60 8a 2c a0 76 1a e1 dc  98 d6 00 7a a4 2f f3 92  |`.,.v......z./..|
...

判断标准非常明确:etcd 中的键值前缀应为 k8s:enc:aescbc:v1:key1,它表示数据使用了 aescbc 加密提供者、并以名为 key1 的加密密钥完成了加密。如果看到的是明文 mydatak8s\x00 这样的 identity 前缀,说明加密配置未生效,应回到加密配置与 API Server 启动参数两处排查。

加密链路在仓库配置中的来源

这个前缀不是偶然出现的,它由三处仓库配置共同决定:

  1. 加密配置文件 configs/encryption-config.yaml
kind: EncryptionConfiguration
apiVersion: apiserver.config.k8s.io/v1
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: ${ENCRYPTION_KEY}
      - identity: {}

其中 name: key1 正是输出中 key1 前缀的来源;${ENCRYPTION_KEY} 是在 docs/06-data-encryption-keys.md 中通过 head -c 32 /dev/urandom | base64 生成、并用 envsubst 注入的真实密钥(32 字节随机数经 base64 编码,恰好满足 AES-128 密钥长度)。配置中列出了两个提供者:aescbc 在前、identity 兜底在后——加密时按列表顺序取第一个可用提供者(aescbc),解密时则按前缀匹配对应提供者。identity: {} 只负责读取历史明文数据,不会让新写入的 Secret 落盘为明文。

  1. API Server 启动参数 units/kube-apiserver.service
--encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \

该参数是加密能力生效的开关:etcd 后端存储的读写经过此配置指定的加密提供者链,密文前缀(k8s:enc:<provider>:<version>:<keyname>)由 API Server 在写入时添加、读取时剥离。若 API Server 未带此参数启动,Secret 将以明文存入 etcd,冒烟测试的 hexdump 结果会直接暴露这一点。

  1. etcd 数据面 units/etcd.service:etcd 仅监听 127.0.0.1:2379 且数据目录为 /var/lib/etcd,这意味着上面 ssh root@server 后在本地执行 etcdctl 才能读到数据,也符合"仅 API Server 可访问 etcd"的最小暴露面设计。

验证二:Deployment 的创建与管理

接下来验证集群具备运行与管理无状态负载的能力:

kubectl create deployment nginx \
  --image=nginx:latest

列出该 Deployment 创建的 Pod:

kubectl get pods -l app=nginx
NAME                     READY   STATUS    RESTARTS   AGE
nginx-56fcf95486-c8dnx   1/1     Running   0          8s

从 Pod 名称 nginx-56fcf95486-c8dnx 可以读出完整的控制链路:nginx 是 Deployment 名,56fcf95486 是其下属 ReplicaSet 的模板哈希后缀,最后一段 c8dnx 才是 Pod 自身的随机标识。kubectl create deployment 会级联创建 Deployment → ReplicaSet → Pod 三层对象,而 Pod 最终被调度到 node-0node-1 上,由 kubelet 交给 containerd 运行。工作节点侧 kubelet 的行为(如单节点最多 16 个 Pod、systemd cgroup 驱动)由 configs/kubelet-config.yaml 中的 maxPods: 16cgroupDriver: systemd 等参数约束。1/1 Running 状态说明 kubelet 成功与 API Server 完成了心跳、探针与状态回写,调度器和控制器-manager 的闭环均正常。

验证三:端口转发(Port Forwarding)

端口转发验证的是"从集群外部直接访问容器端口"这一调试能力。先用 jsonpath 取出 nginx Pod 的完整名称:

POD_NAME=$(kubectl get pods -l app=nginx \
  -o jsonpath="{.items[0].metadata.name}")

然后把本地 8080 端口转发到该 Pod 的 80 端口:

kubectl port-forward $POD_NAME 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

kubectl port-forward 会经 kubeconfig 中配置的 API 端点建立连接,由 API Server 通过 kubelet 的端口转发子资源打通到容器网络——因此即使节点 IP 从 jumpbox 不可直接路由,只要 API 链路通,转发就能工作。

在新终端中通过转发地址发起 HTTP 请求:

curl --head http://127.0.0.1:8080
HTTP/1.1 200 OK
Server: nginx/1.27.4
Date: Sun, 06 Apr 2025 17:17:12 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Wed, 05 Feb 2025 11:06:32 GMT
Connection: keep-alive
ETag: "67a34638-267"
Accept-Ranges: bytes

200 OKnginx 响应头表明请求完整穿过了"jumpbox → API Server → kubelet → 容器网络 → nginx"这条链路。验证完成后,切回原终端按 Ctrl+C 停止转发,会看到 Handling connection for 8080 的提示,随后通道关闭。

验证四:容器日志(Logs)

日志能力验证的是"从 API 而非直接登录节点获取容器输出"的机制:

kubectl logs $POD_NAME
...
127.0.0.1 - - [06/Apr/2025:17:17:12 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.88.1" "-"

注意日志中出现了刚才 curl --head 那条 HEAD / 请求,这同时印证了端口转发的流量确实到达了容器内的 nginx。kubectl logs 的底层路径是 API Server 调用 kubelet 的 /log 端点,再由 containerd 提供容器 stdout 文件,因此它要求 kubelet 与 CRI 工作正常,是排查容器级问题最常用的第一手段。

验证五:容器内执行命令(Exec)

最后一项调试能力验证是在运行中的容器里执行命令:

kubectl exec -ti $POD_NAME -- nginx -v
nginx version: nginx/1.27.4

kubectl exec 经由 API Server 的 exec 子资源、通过 SPDY/websocket 流代理到 kubelet,再经 CRI 进入容器 runtime 执行。该验证通过说明"交互式调试容器"这条链路(认证、kubelet 的 exec 权限、containerd 的 exec 实现)全部可用。

验证六:通过 NodePort Service 暴露应用

暴露 Deployment

用 NodePort 类型的 Service 暴露 nginx Deployment:

kubectl expose deployment nginx \
  --port 80 --type NodePort

注意:LoadBalancer 类型的 Service 在本教程中不可用,因为集群没有配置云厂商负载均衡集成,而这超出了教程范围。

取出 NodePort 与 Pod 所在节点

NODE_PORT=$(kubectl get svc nginx \
  --output=jsonpath='{range .spec.ports[0]}{.nodePort}')
NODE_NAME=$(kubectl get pods \
  -l app=nginx \
  -o jsonpath="{.items[0].spec.nodeName}")

nodePort 由 API Server 自动分配,分配范围受 units/kube-apiserver.service--service-node-port-range=30000-32767 约束,因此取到的端口必然落在 30000–32767 之间。nodeName 则指明 Pod 被调度到了哪个 worker 节点。

发起跨节点请求

curl -I http://${NODE_NAME}:${NODE_PORT}
Server: nginx/1.27.4
Date: Sun, 06 Apr 2025 17:18:36 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Wed, 05 Feb 2025 11:06:32 GMT
Connection: keep-alive
ETag: "67a34638-267"
Accept-Ranges: bytes

这一步的意义在于它验证了完整的 Service 数据面:请求打到某台节点的 30000–32767 端口,由该节点上的 kube-proxy 通过 iptables 规则 DNAT 到目标 Pod 的集群 IP(10.200.x.x 网段),再经节点上 docs/11-pod-network-routes.md 配好的路由把包投递给 Pod 所在节点。仓库中 configs/kube-proxy-config.yaml 固定了这条链路的两个关键参数:

kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
clientConnection:
  kubeconfig: "/var/lib/kube-proxy/kubeconfig"
mode: "iptables"
clusterCIDR: "10.200.0.0/16"

mode: "iptables" 决定了 kube-proxy 以内核规则而非用户态转发来处理流量;clusterCIDR: "10.200.0.0/16" 则覆盖了两个 worker 的 Pod 子网(node-010.200.0.0/24node-110.200.1.0/24),与 docs/03-compute-resources.md 中机器数据库规划的 Pod 网段一致。若 curl 能取到 nginx 响应头,则说明 kube-proxy、Pod 路由与 Service 对象三者协同正常。

小结:冒烟测试覆盖的验收面

把本实验室的验证项汇总,它们恰好覆盖了自举集群最容易出问题的几个层面:

验证项 命令 通过的判定依据 关联配置来源
Secret 静态加密 etcdctl get + hexdump 键值前缀为 k8s:enc:aescbc:v1:key1 configs/encryption-config.yamlunits/kube-apiserver.service
Deployment kubectl create deployment / get pods Pod 处于 1/1 Running configs/kubelet-config.yaml
端口转发 kubectl port-forward + curl 返回 200 OK 及 nginx 响应头 kubeconfig(docs/10-configuring-kubectl.md
日志 kubectl logs 能看到容器 stdout 与访问记录 kubelet + containerd 链路
Exec kubectl exec 容器内命令正常执行 kubelet + CRI 链路
NodePort Service kubectl expose + curl -I 节点 IP + NodePort 返回 nginx 响应头 configs/kube-proxy-config.yaml--service-node-port-range

至此,集群从控制面数据加密、调度与容器运行时,到 Service 网络链路都经过了实证验证。全部通过之后,即可进入下一步 Cleaning Up(清理资源),把为教程创建的虚拟机与资源删除;若想从头再来一遍,可回到 README 重新开始整个引导流程。

登录后查看全文
热门项目推荐
相关项目推荐