Kubernetes The Hard Way 冒烟测试:验证 Secret 静态加密、Deployment 与 NodePort 服务链路
在 Kubernetes The Hard Way 教程中,冒烟测试(Smoke Test)是完成全部控制面与节点引导后的最后一道功能验收环节。本篇以 docs/12-smoke-test.md 为主线,带你在一套单控制面节点(server)加两个工作节点(node-0、node-1)的集群上,逐项验证四项关键能力:Secret 数据在 etcd 中的静态加密、Deployment 的创建与管理、端口转发/日志/容器内执行等调试手段,以及通过 NodePort Service 暴露应用。读完后,你不仅掌握这套验收命令,还能理解每个验证结果背后的组件配置来源,从而在测试失败时快速定位到具体配置项。
冒烟测试执行前的集群状态
进入本篇之前,你需要已经按教程顺序完成以下各阶段(详见 Labs 列表):
- 三台集群机器的准备、SSH 免密与主机名解析(docs/03-compute-resources.md);
- CA 与 TLS 证书、Kubernetes 认证配置文件(docs/04-certificate-authority.md、docs/05-kubernetes-configuration-files.md);
- 数据加密配置与密钥的生成(docs/06-data-encryption-keys.md);
- etcd、控制面组件与 worker 节点的引导(docs/07-bootstrapping-etcd.md、docs/08-bootstrapping-kubernetes-controllers.md、docs/09-bootstrapping-kubernetes-workers.md);
kubectl的 kubeconfig 配置(docs/10-configuring-kubectl.md)与 Pod 网络路由(docs/11-pod-network-routes.md)。
本教程使用的组件版本为 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 的加密密钥完成了加密。如果看到的是明文 mydata 或 k8s\x00 这样的 identity 前缀,说明加密配置未生效,应回到加密配置与 API Server 启动参数两处排查。
加密链路在仓库配置中的来源
这个前缀不是偶然出现的,它由三处仓库配置共同决定:
- 加密配置文件 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 落盘为明文。
- 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 结果会直接暴露这一点。
- 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-0 或 node-1 上,由 kubelet 交给 containerd 运行。工作节点侧 kubelet 的行为(如单节点最多 16 个 Pod、systemd cgroup 驱动)由 configs/kubelet-config.yaml 中的 maxPods: 16、cgroupDriver: 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 OK 与 nginx 响应头表明请求完整穿过了"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-0 的 10.200.0.0/24 与 node-1 的 10.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.yaml、units/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 重新开始整个引导流程。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00