Kubernetes:部署配置、服务身份与持久卷
本篇以Kubernetes 单副本计数项目为例;前置是context、namespace、Secret 与 PVC。源码与输入与下文配置共用同一份文件,示例未执行。
项目与不可省略的前置
Kubernetes 项目 示例未执行。examples/m5-kubernetes 是独立完整配置项目,包含标准库 Python 服务、依赖声明、Dockerfile、构建白名单和一个多文档 resources.yaml。资源包括 Namespace、ServiceAccount、ConfigMap、Secret、PVC、Deployment、Service、Role 和 RoleBinding,未依赖未提供的 Helm chart 或应用模块。
程序提供 /live、/ready 和 /;/ 必须携带 Bearer token 才增加计数。启动时 token 若为空、缺失或仍是 replace-before-use 就直接失败,避免默认值看起来可用。HTTPServer、GET 增量与简单 token 只供本地理论实验,不是互联网认证方案。
未来自跑前需自行构建镜像并发布到目标节点可访问的 Registry,在 resources.yaml 替换 .invalid 镜像地址和 token。若仓库私有,还要由平台提供只读 imagePullSecret 并显式加入 PodSpec;本例不假造凭据。默认 StorageClass、CSI 文件权限支持、CNI/DNS 和隔离命名空间授权也必须事先具备。资源清单没有创建 PV/StorageClass,因为这些属于平台供给,不能拿 hostPath 冒充通用持久存储。
完整源码与镜像输入
import json
import os
import signal
import sqlite3
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
DATA = Path("/data")
TOKEN = Path("/run/secrets/token").read_text().strip()
if not TOKEN or TOKEN == "replace-before-use":
raise ValueError("set a non-placeholder token before deployment")
DATA.mkdir(parents=True, exist_ok=True)
with sqlite3.connect(DATA / "counter.db") as db:
db.execute("CREATE TABLE IF NOT EXISTS counter (id INTEGER PRIMARY KEY, value INTEGER NOT NULL)")
db.execute("INSERT OR IGNORE INTO counter VALUES (1, 0)")
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
try:
if self.path == "/live":
payload = {"live": True}
elif self.path in ("/", "/ready"):
if self.path == "/" and self.headers.get("Authorization") != "Bearer " + TOKEN:
self.send_error(401)
return
with sqlite3.connect(DATA / "counter.db", timeout=2) as db:
if self.path == "/":
db.execute("UPDATE counter SET value = value + 1 WHERE id = 1")
count = db.execute("SELECT value FROM counter WHERE id = 1").fetchone()[0]
payload = {"count": count, "message": os.environ["MESSAGE"]}
else:
self.send_error(404)
return
body = json.dumps(payload).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
except Exception as error:
self.log_error("request failed: %s", type(error).__name__)
self.send_error(503)
server = HTTPServer(("0.0.0.0", 8080), Handler)
def stop(signum, frame):
threading.Thread(target=server.shutdown, daemon=True).start()
signal.signal(signal.SIGTERM, stop)
signal.signal(signal.SIGINT, stop)
try:
server.serve_forever(poll_interval=0.2)
finally:
server.server_close()# Python 3.13 standard library only; no third-party packages.FROM python:3.13-slim
WORKDIR /app
COPY --chown=10001:10001 app.py requirements.txt ./
USER 10001:10001
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
EXPOSE 8080
ENTRYPOINT ["python", "/app/app.py"]*
!app.py
!requirements.txt
!Dockerfile全部 Kubernetes 资源
Kubernetes 完整资源 使用单副本 Recreate 接受更新停机;PVC 1Gi 由默认存储类制备。fsGroup 10001 帮助支持该机制的卷让非 root 进程可写,不保证所有驱动/NFS 后端都能自动改权限。Secret 目录只读,权限 0444 是容器内文件可读设置,命名空间权限仍由 RBAC 控制;单容器可见不是 Secret 加密。
应用 ServiceAccount 不挂载 API token,也没有 RBAC 权限。observer Role 授给单独外部组,只看 Pod、日志、Service、PVC、Deployment 和 EndpointSlice;没有读 Secret、写 Pod 或 exec 权限。该组身份必须由集群认证系统映射,清单本身不会创建登录账号。
完整文件:resources.yaml 完整源码。保留原始内容,使用前请核对本文的版本与环境说明。
正常与故障结果
Kubernetes 成功 预期是 PVC Bound、Deployment 一个可用副本、Pod Ready、Service 的 EndpointSlice 有就绪后端。/ready 200 不增加计数;带正确 Authorization 的 / 第一次返回 count: 1,不带 token 返回 401。重建 Pod 后同一 PVC 数据应保留,ConfigMap MESSAGE 应出现在返回体。以上来自源码推演,未收到实际集群反馈。
| 故障输入 | 预期现象 | 区分与恢复 |
|---|---|---|
| 不替换镜像地址 | ImagePullBackOff 或拉取错误 | 改成已发布且节点可访问的镜像,不靠改探针解决 |
| 不替换 token | 进程启动 ValueError,随后可能 CrashLoopBackOff | 更新 Secret 后受控重建,保留启动日志 |
| 没有默认存储类 | PVC Pending,Pod 无法挂载/调度 | 平台提供合适存储类或预制 PV,不能删 PVC 试运气 |
| /data 权限不符 | SQLite 初始化报错 | 核对 fsGroup、驱动和后端拥有者,不开 privileged |
| Service selector 写错 | Pod Ready 但 Service 无匹配端点 | 对照 Pod 标签与 selector |
| MESSAGE 变更未重建 | 返回旧 message | 环境变量只在创建时读取,需要新 Pod |
| 数据库读取失败 | /live 可能 200,/ready 503,Pod 不就绪 | 检查卷与应用错误,避免 liveness 依赖数据库造成反复重启 |
自跑与清理
README 写出读者自行构建、替换、提交、观察与回收的顺序,所有命令均未执行。删除 Namespace 会一并删除 PVC,后端是否删除取决于 PV reclaimPolicy 与平台,因此数据归档和恢复验证必须发生在清理之前。
# m5-kubernetes:读者自行运行说明
示例未执行。没有启动集群、容器、服务或模拟器,没有运行kubectl dry-run。以下是未来读者在隔离测试环境的自跑说明。
前置:获准的Linux节点集群,稳定apps/v1/core/v1/RBAC v1 API,CNI/DNS,默认StorageClass及支持fsGroup/文件系统卷的CSI,节点可访问的Registry,以及创建专用Namespace和相应RBAC的授权。无默认存储类会PVC Pending,这是平台前置,不会自动部署存储插件。应用依赖Python3.13标准库,无第三方安装。Windows只作为编辑/操作客户端,不使用Windows容器。
1. 自行核验基础镜像和目标CPU平台。从本目录构建并推送到自己的Registry,用真实、节点可访问的引用或digest替换resources.yaml的.invalid地址。私有Registry还需平台提供imagePullSecret,并加入PodSpec。
2. 在隔离副本中把Secret token改成自行产生的非占位值,不提交真实凭据;应用会拒绝replace-before-use。确认1Gi存储配额和后端回收策略。
3. 确认上下文后,由有权限的实验操作者执行:
```bash
docker build -t YOUR_REGISTRY/m5-counter:YOUR_TAG .
docker push YOUR_REGISTRY/m5-counter:YOUR_TAG
kubectl --context=YOUR_CONTEXT apply -f resources.yaml
kubectl --context=YOUR_CONTEXT -n m5-counter get pvc,pod,deployment,service
kubectl --context=YOUR_CONTEXT -n m5-counter rollout status deployment/counter
kubectl --context=YOUR_CONTEXT -n m5-counter get endpointslices
kubectl --context=YOUR_CONTEXT -n m5-counter port-forward service/counter 18081:80
```
apply Namespace/RBAC需要相应授权,observer组不具备写入和port-forward权限。外部组m5-counter-observers必须由认证系统映射,不是清单自动建立的账号。
在另一个终端测试:
```bash
curl -i http://127.0.0.1:18081/ready
curl -i http://127.0.0.1:18081/
curl -H 'Authorization: Bearer YOUR_TOKEN' http://127.0.0.1:18081/
```
预期ready 200;未授权业务请求401;正确token的首个业务请求count=1且message=theory-counter。Pod重建后相同PVC应保留计数,但port-forward绑定的Pod可能已终止,需重新建立转发。错误镜像应拉取失败,原样token应启动失败,无默认StorageClass应PVC Pending。每次只改变一个条件并保存事件和previous日志;这些全是推演,不是实测。
ConfigMap和Secret更新后,本例进程不会自动热加载,应受控重建并验证新配置。Recreate单副本有停机,不应扩容多写者。
停止port-forward:在其终端Ctrl+C。清理工作负载但保留PVC:
```bash
kubectl --context=YOUR_CONTEXT -n m5-counter delete deployment counter
kubectl --context=YOUR_CONTEXT -n m5-counter delete service counter
```
先归档需要的数据并实际验证恢复,再由读者决定是否完整删除实验命名空间:
```bash
kubectl --context=YOUR_CONTEXT delete namespace m5-counter
```
删除Namespace会删除PVC;后端PV是Delete还是Retain由存储类和PV策略决定,可能永久删除数据或留下需管理员清理的资产。不要删除共享StorageClass、PV或其他命名空间来清理本实验。项目练习与解答
问题:把 replicas 改成 3 是可用性优化吗?答案:不是。单个 RWO 卷可能无法跨节点挂载,同节点多进程写入仍有 SQLite 并发与故障边界;Recreate 也不产生数据副本。需要高可用数据库时要重新设计数据层,不能只调副本数。
问题:observer 能转发端口吗?答案:该 Role 没有 pods/portforward 创建权限,不能。自跑的 port-forward 应由另一个具有所需权限的实验操作者使用,这正是观察权限与主动调试权限的区别。
继续阅读
- 知识专题
- 知识专题
- 命令