从探针到权限:一篇吃透 Kubernetes 健康检查、RBAC 认证授权与 Dashboard 可视化管理
摘要 :容器进程在跑 ≠ 服务能用。本文用「反例演示 + 逐条命令验证」的方式,一次性讲透 Kubernetes 三大核心主题:Health Check 健康探针(liveness/readiness/startup × httpGet/exec/tcpSocket) 、认证与授权(X509 用户、Role/ClusterRole、ServiceAccount) 、Dashboard 可视化管理(Kuboard + Kubernetes Dashboard)。所有实验均可复现,附完整 YAML 与排错要点。
关键词:Kubernetes、健康检查、livenessProbe、readinessProbe、RBAC、ServiceAccount、X509 证书、Dashboard、Kuboard
一、开篇:为什么容器 Running 不代表服务可用
应用会因为各种原因变得 unhealthy:临时连接断开、配置错误、程序自身 Bug、死锁(进程还在,但再也处理不了请求)。
kubectl get pod 显示 Running,只说明容器主进程没退出,并不代表业务能正常响应。K8s 的判断逻辑是:
- 进程退出 → 容器崩溃 → 重启(这是 K8s 默认就能做到的)
- 进程还在但已经不干活 → K8s 默认无能为力 → 必须靠 探针(Probe)
kubelet通过探针周期性探测容器中的应用,决定什么时候重启容器 、什么时候把容器从 Service 后端摘掉。这就是本文的主角。
二、Health Check 健康检查
环境准备
bash
root@master30:~# kubectl create ns health
root@master30:~# kubectl config set-context --current --namespace health
实验镜像说明 :本文统一使用内部仓库
hub.laoma.cloud/library/httpd,公网环境可替换为httpd:2.4或nginx:alpine(注意 nginx 主页路径是/usr/share/nginx/html/index.html)。
2.1 反例:没有探针会发生什么
先创建一个不带任何探针的 Pod,看看问题有多隐蔽:
bash
# 创建一个普通 pod
root@master30:~# kubectl run web --image=hub.laoma.cloud/library/httpd --image-pull-policy=IfNotPresent
pod/web created
# 查看 Pod IP
root@master30:~# kubectl describe pod web | grep '^IP:'
IP: 10.224.73.16
# 正常访问,返回 "It works!"
root@master30:~# curl 10.224.73.16
<html><body><h1>It works!</h1></body></html>
现在手工制造故障:删掉主页文件,模拟"应用数据丢失 / 依赖资源不可用"。
bash
# 删除主页文件
root@master30:~# kubectl exec web -- rm -f htdocs/index.html
# 再次访问,已经变成目录列表页(业务已损坏)
root@master30:~# curl 10.224.73.16
<!DOCTYPE HTML PUBLIC "-//W3C/DTD HTML 3.2 Final//EN">
<html>
<head>
<title>Index of /</title>
</head>
<body>
<h1>Index of /</h1>
<ul></ul>
</body></html>
关键观察点 :业务已经坏了,但 Pod 状态依然是 Running,RESTARTS 为 0,K8s 完全无感知。
bash
root@master30:~# kubectl get pod
NAME READY STATUS RESTARTS AGE
web 1/1 Running 0 51s
结论:这就是"僵尸容器"------活着,但没用。生产环境中如果靠人工发现,往往已经是用户投诉之后了。
bash
# 清理环境
root@master30:~# kubectl delete pod web --force
2.2 三类探针:Liveness / Readiness / Startup
| 探针 | 作用 | 失败后果 | 典型用途 |
|---|---|---|---|
| livenessProbe 存活探针 | 判断容器是否处于 healthy 状态 | kubelet 杀掉并重启容器(重建同名 Pod) | 检测死锁、假死,自动"复活" |
| readinessProbe 就绪探针 | 判断容器是否可以对外提供服务 | 从 Service 的 Endpoints 中摘除该 Pod IP,不重启 | 控制流量入口,实现优雅上线 |
| startupProbe 启动探针 | 判断容器内应用是否已完成初始化 | 失败则重启容器,同 liveness | 保护慢启动容器(如加载大模型、预热缓存) |
核心区别一句话记住:
- liveness 决定"要不要重启你"
- readiness 决定"要不要把流量给你"
- startup 决定"现在还不许动我"
startupProbe 存在的意义 :如果配置了启动探针,那么在它成功之前,liveness 和 readiness 都不会执行。这避免了慢启动应用在还没初始化完成时就被 liveness 判死、反复重启(经典 CrashLoopBackOff 元凶)。
yaml
# startupProbe 典型写法(本文后续实验不深入展开)
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 允许失败 30 次
periodSeconds: 10 # 每 10 秒一次
# 相当于给应用最多 30 × 10 = 300 秒的启动宽限期
2.3 四种检测方式
| 检测方式 | 原理 | 成功判定 | 适用场景 |
|---|---|---|---|
| httpGet | 对容器 IP 的指定端口 + 路径发起 HTTP GET | 状态码 200 ≤ code < 400 | Web 服务(最常用) |
| exec | 在容器内执行指定命令 | 命令退出码为 0 | 无法开放端口、需检查文件/进程 |
| tcpSocket | 对容器 IP 的指定端口发起 TCP 连接 | 端口能连上即为健康(连接后立即关闭也算健康) | 非 HTTP 服务:MySQL、Redis、自定义 TCP |
| grpc | 调用 gRPC 健康检查接口 | 响应状态为 SERVING | gRPC 服务(需实现 health-checking 协议) |
四种方式可混用:一个容器可以同时配置 liveness(tcpSocket)+ readiness(httpGet)。
2.4 probe 通用参数速查表
| 参数 | 是否必选 | 说明 | 默认值 / 最小值 |
|---|---|---|---|
| initialDelaySeconds | 必选 | 容器启动后,等待多久才开始第一次探测 | 0 |
| timeoutSeconds | 必选 | 单次探测超时时间,超时即判失败 | 默认 1s,最小 1s |
| periodSeconds | 可选 | 探测间隔(检查频率) | 默认 10s,最小 1s |
| successThreshold | 可选 | 连续成功多少次才算"健康" | 默认 1,最小 1(liveness 必须为 1) |
| failureThreshold | 可选 | 连续失败多少次才算"不健康" | 默认 3,最小 1 |
排错口诀 :Pod 反复重启先看 liveness;Pod Running 但
READY 0/1且 Service 访问不到,先看 readiness;Pod 一启动就被杀,把initialDelaySeconds调大或改用startupProbe。
2.5 实战:httpGet 之 livenessProbe
场景 :主页文件被删除 → 探测 /index.html 返回 404 → 连续失败 3 次 → 容器重启 → 文件恢复(因为镜像里本来就有)。
bash
root@master30:~# vim deploy-httpGet-liveness.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 1 # 副本数量,启动1个pod
selector:
matchLabels:
app: web # 匹配pod标签,关联模板
template:
metadata:
labels:
app: web # pod标签,和selector保持一致
spec:
containers:
- image: hub.laoma.cloud/library/httpd # 镜像地址,httpd网页服务
imagePullPolicy: IfNotPresent # 本地有镜像就不拉取,没有才拉
name: httpd # 容器名称
# ===== 添加 livenessProbe 存活探针 =====
livenessProbe:
failureThreshold: 3 # 连续失败3次,判定容器异常,重启Pod
initialDelaySeconds: 5 # 容器启动后,延迟5s才开始第一次探测
periodSeconds: 10 # 每隔10秒执行一次存活探测
successThreshold: 1 # 失败后连续1次成功就恢复正常,存活探针固定为1
timeoutSeconds: 5 # 单次探测请求超时时间,超过5s即判定失败
httpGet:
path: /index.html # HTTP健康检查访问路径
port: 80 # httpd默认端口80
scheme: HTTP # 请求协议,HTTP/HTTPS
部署与验证:
bash
# 创建应用
root@master30:~# kubectl apply -f deploy-httpGet-liveness.yaml
deployment.apps/web created
root@master30:~# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-85c6ff748f-qwszz 1/1 Running 0 12m
# 查看 Pod IP
root@master30:~# kubectl describe pod web-85c6ff748f-qwszz | grep '^IP:'
IP: 10.98.146.216
制造故障:
bash
# 删除主页文件
root@master30:~# kubectl exec web-85c6ff748f-qwszz -- bash -c 'rm htdocs/index.html'
等待约 30~35 秒(5s 首次延迟 + 3 次 ×10s 间隔),观察 Pod:
bash
root@master30:~# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-85c6ff748f-qwszz 1/1 Running 1 13m
RESTARTS 变成 1 ------ 容器被 kubelet 判定不健康并重启,镜像自带的主页文件随之恢复:
bash
root@master30:~# curl 10.98.146.216
<html><body><h1>It works!</h1></body></html>
注意 :容器删除需要一点时间,由
terminationGracePeriodSeconds(默认 30s)控制。只有等旧容器删除、新容器创建完成后,探测才会继续。这也是为什么重启后不会立刻看到 RESTARTS 变化。
查看失败原因(排错利器):
bash
root@master30:~# kubectl describe pod web-85c6ff748f-qwszz
...
Events:
Warning Unhealthy kubelet Liveness probe failed: HTTP probe failed with statuscode: 404
Normal Killing kubelet Container httpd failed liveness probe, will be restarted
bash
# 清理环境
root@master30:~# kubectl delete deployments.apps web
2.6 实战:httpGet 之 readinessProbe
场景 :3 副本 + Service。删掉其中一个 Pod 的主页 → 该 Pod 被判定"未就绪" → 从 Endpoints 摘除 → 流量不再转发给它,且 Pod 不会被重启。
bash
root@master30:~# vim deploy-httpGet-readiness.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- image: hub.laoma.cloud/library/httpd
imagePullPolicy: IfNotPresent
name: httpd
# ===== 添加 readinessProbe 部分 =====
readinessProbe:
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
httpGet:
path: /index.html
port: 80
scheme: HTTP
部署并暴露服务:
bash
# 创建应用
root@master30:~# kubectl apply -f deploy-httpGet-readiness.yaml
deployment.apps/web created
root@master30:~# kubectl expose deployment web --port=80 --target-port=80
service/web exposed
root@master30:~# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.98.146.216 <none> 80/TCP 4m5s
root@master30:~# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-9479dc55c-6bpg7 1/1 Running 0 2m34s
web-9479dc55c-d2gbn 1/1 Running 0 2m34s
web-9479dc55c-hqh8q 1/1 Running 0 2m34s
给 3 个 Pod 写入不同主页,方便观察负载均衡效果:
bash
# 准备 3 个 pod 主页文件
root@master30:~# for pod in $(kubectl get pods -o name | awk -F / '{print $2}'); do \
kubectl exec $pod -- bash -c "echo $pod > htdocs/index.html"; \
done
通过 Service 访问,验证负载均衡:
bash
# 用变量保存 ClusterIP,避免手抄 IP 出错
root@master30:~# SVC_IP=$(kubectl get svc web -o jsonpath='{.spec.clusterIP}')
root@master30:~# for i in {1..90}; do curl -s $SVC_IP; done | sort | uniq -c
24 web-9479dc55c-6bpg7
33 web-9479dc55c-d2gbn
33 web-9479dc55c-hqh8q
# 查看 Service 后端 Endpoints
root@master30:~# kubectl get endpoints web
NAME ENDPOINTS AGE
web 10.224.73.15:80,10.224.73.34:80,10.224.73.35:80 21m
制造故障 :删掉 web-9479dc55c-6bpg7 的主页文件。
bash
root@master30:~# kubectl exec -it web-9479dc55c-6bpg7 -- rm -f htdocs/index.html
观察 Endpoints ------ 故障 Pod 的 IP 已被摘除:
bash
root@master30:~# kubectl get endpoints web
NAME ENDPOINTS AGE
web 10.224.73.15:80,10.224.73.34:80 23m
再次访问 Service,流量不再打到故障 Pod:
bash
root@master30:~# for i in {1..90}; do curl -s $SVC_IP; done | sort | uniq -c
50 web-9479dc55c-d2gbn
40 web-9479dc55c-hqh8q
观察 Pod 状态 :READY 变成 0/1,但 RESTARTS 依然是 0 ------ 这正是 readiness 与 liveness 的本质区别。
bash
root@master30:~# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-9479dc55c-6bpg7 0/1 Running 0 8m49s
web-9479dc55c-d2gbn 1/1 Running 0 8m49s
web-9479dc55c-hqh8q 1/1 Running 0 8m49s
生产价值 :readiness 是"只摘流量、不杀容器",特别适用于依赖外部数据库/缓存的场景------DB 抖动时先摘流量自保,DB 恢复后自动回归,避免无谓重启导致的冷启动雪崩。
bash
# 清理环境
root@master30:~# kubectl delete deployments.apps web
root@master30:~# kubectl delete svc web
2.7 实战:exec 检测
原理 :kubelet 在容器内执行指定命令,退出码为 0 判定成功,非 0 判定失败。
示例 1:检测容器自带文件
bash
root@master30:~# vim deploy-exec-liveness.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- image: hub.laoma.cloud/library/httpd
imagePullPolicy: IfNotPresent
name: httpd
# ===== 添加 livenessProbe 部分 =====
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 10
exec:
command:
- cat
- /usr/local/apache2/htdocs/index.html
bash
# 创建应用
root@master30:~# kubectl apply -f deploy-exec-liveness.yaml
deployment.apps/web created
root@master30:~# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-8c9ff9b76-nm6s2 1/1 Running 1 (2s ago) 18s
# 删除主页文件
root@master30:~# kubectl exec web-8c9ff9b76-nm6s2 -- bash -c 'rm htdocs/index.html'
# 观察 pod 状态,RESTARTS 次数变为 1
root@master30:~# kubectl get pod
NAME READY STATUS RESTARTS AGE
web 1/1 Running 1 4m5s
注意路径 :httpd 官方镜像的工作目录是
/usr/local/apache2,kubectl exec时相对路径htdocs/index.html等价于此目录下的htdocs/index.html,但探针里必须写绝对路径。
示例 2:检测自定义健康标记文件(经典官方示例)
这个例子演示"应用自己控制健康状态":前 10 秒文件存在(健康),之后删除文件(不健康),容器被重启,重启后文件又被创建,如此循环。
bash
# 生成 Pod 骨架
root@master30:~# kubectl run busybox --image=busybox --image-pull-policy=IfNotPresent \
-o yaml --dry-run=client > busybox.yml
root@master30:~# vim deploy-exec-busybox.yml
yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: busybox
name: busybox
spec:
containers:
- image: busybox
imagePullPolicy: IfNotPresent
name: busybox
# ===== 添加 args 参数:模拟"健康 10 秒,然后不健康" =====
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy; sleep 100
# ===== 添加 livenessProbe 参数 =====
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 10
exec:
command:
- ls
- /tmp/healthy
dnsPolicy: ClusterFirst
restartPolicy: Always
bash
root@master30:~# kubectl apply -f deploy-exec-busybox.yml
pod/busybox created
# 持续观察:RESTARTS 会不断增长
root@master30:~# kubectl get pod busybox -w
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 0 5s
busybox 1/1 Running 1 (5s ago) 35s
busybox 1/1 Running 2 (5s ago) 70s
时间线拆解:
| 时间 | 事件 |
|---|---|
| 0s | 容器启动,创建 /tmp/healthy |
| 5s | 首次探测(initialDelaySeconds=5),文件存在 → 成功 |
| 10s | 脚本 sleep 10 结束,删除 /tmp/healthy |
| 15s / 20s / 25s | 连续 3 次探测失败 → 达到 failureThreshold=3 |
| ~25s | kubelet 杀死并重启容器 → RESTARTS +1,循环开始 |
2.8 实战:tcpSocket 检测
原理 :kubelet 尝试与容器指定端口建立 TCP 连接,能连上即健康(注意:对端连接建立后立即关闭,也算健康)。
bash
root@master30:~# vim deploy-tcpSocket-liveness.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- image: hub.laoma.cloud/library/httpd
imagePullPolicy: IfNotPresent
name: httpd
# ===== 添加 livenessProbe 部分 =====
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 10
tcpSocket:
port: 80
bash
root@master30:~# kubectl apply -f deploy-tcpSocket-liveness.yaml
deployment.apps/web created
root@master30:~# kubectl get pod -w
NAME READY STATUS RESTARTS AGE
web-6d4f8b9c7d-x2k9p 1/1 Running 0 12s
tcpSocket vs httpGet 怎么选?
- tcpSocket 更轻量,只验证"端口在监听",适合 MySQL(3306)、Redis(6379)、SSH 等非 HTTP 服务;
- httpGet 能验证"业务逻辑正常"(返回 200 而不是 500),信息量更大,是 Web 服务首选;
- tcpSocket 无法发现"进程还在、端口还开着、但返回 500"这种假死状态。
2.9 生产场景:Scale Up 与滚动更新
场景 A:Scale Up(扩容)
多副本应用扩容时,新副本会被作为 backend 加入 Service 负载均衡。但应用从容器启动到真正能提供服务,通常需要一个准备阶段:加载缓存数据、连接数据库、预热 JVM/模型。
如果没有 readinessProbe ,容器一 Running 就被塞进 Endpoints → 请求打到"还没准备好"的 Pod → 用户看到 502/超时。
有了 readinessProbe:新副本只有探测通过才会被加入 Service,请求零损失。
场景 B:Rolling Update(滚动更新)
假设现有一个正常运行的多副本应用,现在要更新镜像版本:
- 正常情况下新副本需要 10 秒完成准备工作,在此之前无法响应业务请求;
- 但由于人为配置错误 (例如连不上后端数据库),新副本始终无法完成准备工作。
如果没有配置 Health Check 会怎样?
新副本本身没有异常退出,默认的"健康检查"机制(只看进程是否存活)会认为容器已经就绪,于是逐步用新副本替换现有副本。结果是:当所有旧副本都被替换后,整个应用无法处理任何请求,服务彻底中断。如果发生在生产系统上,后果非常严重。
如果正确配置了 Health Check?
新副本只有通过探测才会被添加到 Service;如果探测始终不通过,滚动更新会卡在新副本,现有旧副本不会被全部替换,业务仍然正常进行 。这正是 K8s 滚动更新 + maxUnavailable 能实现"零中断发布"的底层保障。
一句话总结 :Health Check 是滚动更新的安全阀。 没有它,一次错误的发布就是一次全站宕机。
环境清理
bash
root@master30:~# kubectl delete ns health
三、Kubernetes 认证与授权
环境准备
bash
root@master30:~# kubectl create ns auth
namespace/auth created
root@master30:~# kubectl config set-context --current --namespace auth
Context "kubernetes-admin@kubernetes" modified.
3.1 访问控制全流程
当用户使用 User 或 ServiceAccount 访问 Kubernetes API 时,每个请求都要经过多阶段访问控制才会被接受:
text
┌─────────────────────────────────────────┐
kubectl / curl │ kube-apiserver (6443) │
│ │ │
├──────────►│ ① 认证 Authentication │
│ │ 客户端证书 / Token / SA Token ... │
│ │ 失败 → HTTP 401 Unauthorized │
│ ├─────────────────────────────────────────┤
│ │ ② 鉴权 Authorization │
│ │ RBAC / Node / ABAC / Webhook │
│ │ 失败 → HTTP 403 Forbidden │
│ ├─────────────────────────────────────────┤
│ │ ③ 准入控制 Admission Control │
│ │ Mutating → Validating │
│ │ (仅对写操作生效,可修改/拒绝) │
│ ├─────────────────────────────────────────┤
│ │ ④ 持久化:写入 etcd │
│ └─────────────────────────────────────────┘
│
└── 全程伴随:⑤ 审计 Audit(按时间顺序记录安全相关操作)
各阶段要点:
| 阶段 | 关键点 |
|---|---|
| 传输安全 | API Server 默认在 6443 端口监听,受 TLS 保护(--secure-port 可改端口,--bind-address 可改监听地址)。客户端 ~/.kube/config 中需携带 CA 证书副本以信任连接 |
| ① 认证 | 依次尝试各个认证模块,只要一个成功 即通过;全部失败返回 401 |
| ② 鉴权 | 请求需携带「用户名 + 动作 + 资源对象」;任意一个模块批准即通过;全部拒绝返回 403 。默认拒绝 |
| ③ 准入控制 | 只对创建/修改/删除/连接 生效,读操作不触发;任一控制器拒绝即立即拒绝(无"投票"机制);可设置复杂默认值 |
| ⑤ 审计 | 记录集群中与安全相关的、按时间排序的操作序列 |
3.2 Kubernetes 中的两类用户
| 类别 | 说明 |
|---|---|
| 普通用户(User) | K8s 不提供普通用户对象,由外部认证系统(X509 证书、LDAP、OIDC 等)提供。无法通过 API 添加用户 |
| 服务账号(ServiceAccount) | 由 K8s API 管理的用户,绑定到特定 namespace,凭据挂载进 Pod,供集群内进程访问 API |
核心认知 :K8s 认为「能提供由集群 CA 签名的合法证书的用户,就是通过认证的用户 」,并用证书中 subject 的 CN(Common Name) 字段(如
/CN=bob)作为用户名,用 O(Organization) 字段作为用户组。随后由 RBAC 决定该用户能做什么。
认证成功后,认证插件会为请求关联以下属性供鉴权使用:
- 用户名 (username):如
kubernetes-admin、jane@example.com - 用户 ID(uid):比用户名更具一致性与唯一性
- 用户组 (groups):如
system:masters、devops-team - 附加字段(extra):额外的键值映射
3.3 认证插件全家福
K8s 支持同时开启多个认证插件,任意一个通过即可;全部失败返回 401。
| 认证方式 | 启用方式 / 说明 | 备注 |
|---|---|---|
| X509 客户证书 | API Server 启动参数 --client-ca-file=SOMEFILE |
默认使用,本文重点 |
| 静态令牌文件 | --token-auth-file=SOMEFILE,CSV 格式 |
默认未启用 |
| 启动引导令牌 | --enable-bootstrap-token-auth,以 Secret 存于 kube-system |
kubeadm 自动配置 |
| 服务账号令牌 | 自动启用,JWT 形式 | 默认使用,本文重点 |
| Webhook 令牌 | HTTP 回调验证持有者令牌 | 对接外部认证系统 |
| 身份认证代理 | 从请求头(如 X-Remote-User)识别用户 |
配合认证代理使用 |
| basic-auth-file | K8s 1.19 起已废弃 | 不再可用 |
bash
# 查看 X509 客户端 CA 配置
root@master30:~# cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep -- --client-ca-file
- --client-ca-file=/etc/kubernetes/pki/ca.crt
# 查看静态令牌文件(默认未启用,无输出)
root@master30:~# cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep -- --token-auth-file
# 查看启动引导令牌开关
root@master30:~# cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep bootstrap
- --enable-bootstrap-token-auth=true
静态令牌文件格式(CSV,至少三列):
bash
token,user,uid,"group1,group2,group3"
匿名请求
如果请求没有被任何已配置的认证方法拒绝 ,则被视为匿名请求,用户名 system:anonymous,用户组 system:unauthenticated。
- 1.5.1~1.5.x:默认禁用
- 1.6 及以后 :只要鉴权模式不是
AlwaysAllow,匿名访问默认启用 ;RBAC/ABAC 需要对system:anonymous或system:unauthenticated显式授权 ,旧的user:*/group:*规则不再包含匿名用户
实验现象 :配置了令牌认证且启用匿名访问的服务器上,提供非法令牌 →
401;不提供令牌 → 视为匿名请求。
3.4 实战:X509 证书创建用户
实验拓扑 :client 节点(用户 fb)生成密钥与证书请求 → 发给管理员 → master30 用集群 CA 签名 → 回传 → 客户端组装 kubeconfig → 管理员授权 → 验证。
第一步:客户端准备申请材料
bash
# 客户端需安装与集群版本一致的 kubectl
root@client:~# apt install -y kubectl=1.30.2-1.1
# 创建私钥
root@client:~# openssl genrsa -out fb.key 2048
# 根据私钥创建证书请求(CSR)
root@client:~# openssl req -new -key fb.key -out fb.csr -subj '/CN=fb/O=kubernets'
subject 字段说明:
| 字段 | 含义 |
|---|---|
| C | Country,国家 |
| ST | STate,省份 |
| L | Location,城市 |
| O | Organization,组织/公司(对应 K8s 用户组) |
| OU | Organization Unit,部门 |
| CN | Common Name,对应 K8s 用户名 |
| emailAddress | 联系人邮箱 |
完整示例:
bash
root@client:~# openssl req -new -key servera.key -out servera.csr \
-subj "/C=CHINA/ST=JS/L=NJ/O=LM/OU=DEVOPS/CN=servera.lab.example.com/emailAddress=fb@lab.example.com"
# 客户端将证书请求发给 kubernetes 管理员
root@client:~# scp fb.csr root@master30:
第二步:管理员签名,生成用户证书
bash
# 使用 ca.crt 和 ca.key 签名fb.csr,得到 fb.crt 证书(有效期 1095 天)
root@master30:~# openssl x509 -req -in fb.csr \
-CA /etc/kubernetes/pki/ca.crt \
-CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial -out fb.crt -days 1095
# 导出 kubeconfig 模版
root@master30:~# kubectl config view > config.tpl
# 将 kubeconfig 模板、用户证书和集群 CA 证书发给客户端
root@master30:~# scp config.tpl fb.crt /etc/kubernetes/pki/ca.crt root@client:~
第三步:客户端创建 kubeconfig
方法一(推荐):用 kubectl config 命令逐步生成
先编辑模版 config.tpl:
yaml
apiVersion: v1
clusters:
- cluster:
server: https://10.1.8.30:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
namespace: default
user: fb
name: fb@kubernetes
current-context: fb@kubernetes
kind: Config
users:
- name: fb
然后依次设置 cluster / credentials / context:
bash
root@client:~# mv config.tpl config
# 设置 cluster
root@client:~# kubectl config set-cluster kubernetes --kubeconfig=config \
--certificate-authority=ca.crt --embed-certs
# --kubeconfig 指定 kubeconfig 文件
# --server 指定 kubernetes 服务器 API 地址
# --certificate-authority 指定 CA 证书
# --embed-certs 将 ca.crt 的内容内嵌到 config 文件中(不加则只记录路径)
# 设置 credentials
root@client:~# kubectl config set-credentials fb --kubeconfig=config \
--client-key=fb.key --client-certificate=fb.crt --embed-certs
# --client-key 指定用户私钥
# --client-certificate 指定服务器为用户生成的证书
# 设置 context
root@client:~# kubectl config set-context fb --kubeconfig=config \
--namespace=default --cluster=kubernetes --user=fb
管理员授权(RBAC 部分后文详述):
bash
root@master30:~# kubectl create clusterrolebinding fb-admin \
--clusterrole=cluster-admin --user=fb
clusterrolebinding.rbac.authorization.k8s.io/fb-admin created
验证结果:
bash
root@client:~# kubectl get nodes --kubeconfig=config
NAME STATUS ROLES AGE VERSION
master30.laoma.cloud Ready control-plane 40h v1.30.2
worker31.laoma.cloud Ready <none> 40h v1.30.2
worker32.laoma.cloud Ready <none> 40h v1.30.2
想省事的话,把
config放到~/.kube/config,之后就不用每次带--kubeconfig了。
方法二(不推荐):手工编辑 base64
bash
# 获取 ca.crt 的 base64 编码,填入 certificate-authority-data
root@client:~# cat ca.crt | base64 | tr -d '\n'
# 获取 fb.key 的 base64 编码,填入 client-key-data
root@client:~# cat fb.key | base64 | tr -d '\n'
# 获取 fb.crt 的 base64 编码,填入 client-certificate-data
root@client:~# cat fb.crt | base64 | tr -d '\n'
对应 kubeconfig 结构:
yaml
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://10.1.8.30:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-fb
name: kubernetes-fb@kubernetes
current-context: kubernetes-fb@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-fb
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
删除账户
bash
# 删除权限(关键步骤)
root@master30:~# kubectl delete clusterrolebindings.rbac.authorization.k8s.io fb-admin
# 验证权限
root@client:~# kubectl get nodes
Error from server (Forbidden): nodes is forbidden: User "fb" cannot list resource "nodes" in API group "" at the cluster scope
重要理解 :删除 k8s 中的 CSR 资源后,客户端用户仍然可以访问集群。 因为用户认证由 X509 提供,与 K8s 无关------K8s 只认"证书是不是集群 CA 签的"。
要真正禁止某用户登录,必须从 X509 认证机制下手(例如通过 CA 吊销列表 CRL 将证书加入黑名单),而不是在 K8s 里删资源。
3.5 鉴权模式
bash
root@master30:~# cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep mode
- --authorization-mode=Node,RBAC
默认使用 RBAC 与 Node 两种模式。
| 模式 | 说明 |
|---|---|
| Node | 节点专用,根据调度到 kubelet 上运行的 Pod 为 kubelet 授予权限 |
| RBAC | 基于角色的访问控制,通过 rbac.authorization.k8s.io API 组动态配置策略(生产标准) |
| ABAC | 基于属性的访问控制,策略文件静态,修改需重启 API Server |
| Webhook | 通过 HTTP POST 回调外部服务做鉴权决策 |
| AlwaysAllow | 允许所有请求(仅测试用,极度危险) |
| AlwaysDeny | 拒绝所有请求(不限制 admin 用户) |
多个鉴权模块时的裁决规则:
- 任一模块批准或拒绝 → 立即返回该决定,不再询问其他模块
- 所有模块都"没意见" → 拒绝(默认拒绝),返回 403
实验:切换为 AlwaysAllow 验证认证与鉴权的分离
bash
root@master30:~# vim /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
containers:
- command:
- kube-apiserver
...
- --authorization-mode=AlwaysAllow # 原为 Node,RBAC
修改保存后,K8s 会自动重启 kube-apiserver 静态 Pod,等待其 Running 后验证:
bash
root@client:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master30.laoma.cloud Ready control-plane 12d v1.30.2
worker31.laoma.cloud Ready <none> 12d v1.30.2
worker32.laoma.cloud Ready <none> 12d v1.30.2
结论 :fb 用户的权限是被删除了 (无 ClusterRoleBinding),但因为鉴权模式变成 AlwaysAllow,请求依然通过。这清楚证明:认证(你是谁)与鉴权(你能干什么)是两个独立阶段 。实验完成后请务必改回
Node,RBAC!
3.6 Role 与 ClusterRole 管理
K8s 将一组权限打包成角色(Role),再把角色**绑定(Binding)**给用户/组/SA,用户即继承该角色的权限。
角色分类
| 角色 | 绑定对象 | 生效范围 |
|---|---|---|
| Role | RoleBinding | 只能在当前这一个 namespace生效 |
| ClusterRole | ClusterRoleBinding | 全集群所有 namespace + 集群资源 |
| ClusterRole | RoleBinding | 只在绑定的这一个 namespace 生效(复用 ClusterRole 规则,缩小范围) |
内置 ClusterRole
K8s 预定义了若干 ClusterRole,常用四个:
| ClusterRole | 权限范围 |
|---|---|
| view | 几乎所有对象的 get、list、watch(只读) |
| edit | view 权限 + 部分对象的 create、delete、deletecollection、patch、update |
| admin | 大部分对象的全部权限(含 RBAC 管理) |
| cluster-admin | 所有对象的所有权限(超级管理员) |
bash
root@master30:~# kubectl describe clusterroles admin
Name: admin
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.k8s.io/autoupdate: true
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
rolebindings.rbac.authorization.k8s.io [] [] [create delete deletecollection get list patch update watch]
roles.rbac.authorization.k8s.io [] [] [create delete deletecollection get list patch update watch]
configmaps [] [] [create delete deletecollection patch update get list watch]
endpoints [] [] [create delete deletecollection patch update get list watch]
......
PolicyRule 字段释义:
- Resources:资源类型,如 pods、secrets、configmaps
- Resource Names:特定资源实例名(如指定某个 Secret 名称)
- Non-Resource URLs :非资源 URL(如
/healthz),一般无需关注 - Verbs :动作,包括
get、list、create、delete、update、patch、watch、exec等
get 与 list 的区别(面试常考):
bash
# 只有 list 权限:可以列出清单,但不能获取单个对象详情
root@client:~# kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-6dfcd885bf-lq4n5 1/1 Running 4 52d
...
root@client:~# kubectl get pod kube-proxy-8kp8w -n kube-system
Error from server (Forbidden): pods "kube-proxy-8kp8w" is forbidden: User "fb" cannot get resource "pods" in API group "" in the namespace "kube-system"
bash
# 只有 get 权限:可以获取单个对象,但不能列出清单
root@client:~# kubectl get pod -n kube-system
Error from server (Forbidden): pods is forbidden: User "fb" cannot list resource "pods" in API group "" in the namespace "kube-system"
root@client:~# kubectl get pod kube-proxy-8kp8w -n kube-system
NAME READY STATUS RESTARTS AGE
kube-proxy-8kp8w 1/1 Running 4 52d
创建 Role
bash
root@master30:~# kubectl create role -h
Create a role with single rule.
Usage:
kubectl create role NAME --verb=verb --resource=resource.group/subresource
[--resource-name=resourcename] [--dry-run=server|client|none] [options]
示例 1:命名空间内所有 Pod 的 get、list、watch 权限
bash
root@master30:~# kubectl create role pod-role --verb=get,list,watch --resource=pods -n default --dry-run=client -o yaml
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
creationTimestamp: null
name: pod-role
namespace: default
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
bash
root@master30:~# kubectl create role pod-role --verb=get,list,watch --resource=pods -n default
role.rbac.authorization.k8s.io/pod-role created
root@master30:~# kubectl get roles pod-role -n default
NAME CREATED AT
pod-role 2021-08-24T10:24:36Z
root@master30:~# kubectl describe roles pod-role -n default
Name: pod-role
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
pods [] [] [get list watch]
示例 2:仅能访问特定名称的资源
bash
# 权限规则:只允许 get 两个指定名字的 Pod:`readablepod` 和 `anotherpod`,同命名空间下别的 Pod 不能 get
root@master30:~# kubectl create role pod-role --verb=get --resource=pods \
--resource-name=readablepod --resource-name=anotherpod
示例 3:针对 replicasets 授权
bash
root@master30:~# kubectl create role foo --verb=get,list,watch --resource=replicasets
修改 Role
bash
# 增加 create 权限
root@master30:~# kubectl edit roles -n default pod-role
......
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- get
- watch
- create # 新增
创建 ClusterRole
bash
root@master30:~# kubectl create clusterrole -h
Usage:
kubectl create clusterrole NAME --verb=verb --resource=resource.group
[--resource-name=resourcename] [--dry-run=server|client|none] [options]
bash
# 示例1:所有 namespace 中 Pods 的 get、list、watch
root@master30:~# kubectl create clusterrole pod-role --verb=get,list,watch --resource=pods
# 示例2:仅限指定名称的 Pod
root@master30:~# kubectl create clusterrole pod-reader --verb=get --resource=pods \
--resource-name=readablepod --resource-name=anotherpod
# 示例3:同时授权 pods 与 pods/status 子资源
root@master30:~# kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status
对应 YAML:
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: pod-role
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
bash
root@master30:~# kubectl describe clusterrole pod-role
Name: pod-role
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
pods [] [] [get list watch]
3.7 apiGroups:最容易踩的坑
Role 的 rules.apiGroups 默认为空字符串 ""(即 core 组)。
| 资源类型 | apiVersion | apiGroups |
|---|---|---|
| Pod、Service、PersistentVolume、PersistentVolumeClaim、ConfigMap、Secret、Namespace、Node | v1 | "" |
| Deployment、DaemonSet、StatefulSet、ReplicaSet | apps/v1 | apps |
| Job | batch/v1 | batch |
| CronJob | batch/v1 | batch |
| Role、RoleBinding、ClusterRole、ClusterRoleBinding | rbac.authorization.k8s.io/v1 | rbac.authorization.k8s.io |
| NetworkPolicy、Ingress | networking.k8s.io/v1 | networking.k8s.io |
查询任意资源的 apiVersion:
bash
root@master30:~# kubectl explain deployment | grep VERSION
VERSION: apps/v1
root@master30:~# kubectl explain networkpolicy | grep VERSION
VERSION: networking.k8s.io/v1
# 或者
root@master30:~# kubectl api-resources | grep -i deploy
deployments deploy apps/v1 true Deployment
❌ 错误示例 :apiGroups 写成 "",却想管 deployments ------ 权限不生效!
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployments-role
namespace: default
rules:
- apiGroups:
- "" # ← 错误!应为 "apps"
resources:
- deployments
verbs:
- get
- list
- watch
✅ 正确示例 1:只读 deployments
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployments-role
namespace: default
rules:
- apiGroups:
- "apps"
resources:
- deployments
verbs:
- get
- list
- watch
✅ 正确示例 2 :可 scale deployments(kubectl scale 需要 patch + deployments/scale 子资源)
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployments-role
namespace: default
rules:
- apiGroups:
- "apps"
resources:
- deployments
- deployments/scale # 关键:子资源
verbs:
- get
- list
- watch
- patch # 关键:scale 本质是 patch
✅ 正确示例 3:多组资源分别授权
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: all-role
namespace: default
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- apiGroups:
- "apps"
resources:
- deployments
- deployments/scale
verbs:
- get
- list
- watch
- patch
3.8 绑定、回收与删除
RoleBinding
先 dry-run 看结构:
bash
root@master30:~# kubectl create rolebinding default-pod-fb -n default \
--role=pod-role --user=fb --dry-run=client -o yaml
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: null
name: default-pod-fb
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pod-role
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: fb
正式创建并验证:
bash
# 绑定 ns/default 中角色 pod-role 给 fb
root@master30:~# kubectl create rolebinding default-pod-fb -n default --role=pod-role --user=fb
rolebinding.rbac.authorization.k8s.io/default-pod-fb created
root@master30:~# kubectl get rolebindings -n default default-pod-fb
NAME ROLE AGE
default-pod-fb Role/pod-role 2m15s
root@master30:~# kubectl describe rolebindings -n default default-pod-fb
Name: default-pod-fb
Role:
Kind: Role
Name: pod-role
Subjects:
Kind Name Namespace
---- ---- ---------
User fb
客户端验证:
bash
root@client:~# kubectl get pod -n default
NAME READY STATUS RESTARTS AGE
web 1/1 Running 0 2m30s
root@client:~# kubectl get pod -n default -w
NAME READY STATUS RESTARTS AGE
web 1/1 Running 0 2m42s
⚠️ 重要提醒 :
--user与--username是两个完全不同的选项!
--user:要绑定角色的用户名(RBAC subject)--username:用于 API Server basic 认证的用户名绑错的话,RBAC 会把你当成另一个用户,权限自然不生效。
动态性 :角色绑定完成后,角色权限发生变化,用户获得的权限会跟着动态变化,无需重新绑定。
回收权限 / 删除角色
bash
# 回收:删除绑定
root@master30:~# kubectl delete rolebindings default-pod-fb -n default
rolebinding.rbac.authorization.k8s.io "default-pod-fb" deleted
# 再次验证
root@client:~# kubectl get pod -n default
Error from server (Forbidden): pods is forbidden: User "fb" cannot list resource "pods" in API group "" in the namespace "default"
# 删除角色本身
root@master30:~# kubectl delete roles pod-role -n default
role.rbac.authorization.k8s.io "pod-role" deleted
ClusterRoleBinding
bash
root@master30:~# kubectl create clusterrolebinding fb-pod-role \
--clusterrole=pod-role --user=fb --dry-run=client -o yaml
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
creationTimestamp: null
name: fb-pod-role
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: pod-role
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: fb
bash
root@master30:~# kubectl create clusterrolebinding fb-pod-role --clusterrole=pod-role --user=fb
clusterrolebinding.rbac.authorization.k8s.io/fb-pod-role created
root@master30:~# kubectl describe clusterrolebinding fb-pod-role
Name: fb-pod-role
Role:
Kind: ClusterRole
Name: pod-role
Subjects:
Kind Name Namespace
---- ---- ---------
User fb
# client 节点使用 fb 用户测试权限(所有 namespace 的 Pod 都能看)
root@client:~# kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-6dfcd885bf-lq4n5 1/1 Running 6 53d
calico-node-44cf4 1/1 Running 6 53d
......
root@client:~# kubectl get pod -n default
NAME READY STATUS RESTARTS AGE
web 1/1 Running 1 15h
bash
# 回收
root@master30:~# kubectl delete clusterrolebinding fb-pod-role
# 删除 clusterrole
root@master30:~# kubectl delete clusterrole pod-rule
ClusterRole 的两种绑定模式(重要)
| 绑定方式 | ClusterRole 生效范围 |
|---|---|
| ClusterRoleBinding | 全局生效(所有 namespace) |
| RoleBinding | 仅在该 RoleBinding 所在 namespace 生效 |
这是用 ClusterRole 做"按命名空间授权"的标准做法:定义一份 ClusterRole,在各个 namespace 里用 RoleBinding 分别绑定,避免重复定义多份 Role。
实践 1:角色管理
bash
# 1. 在 auth 命名空间,创建角色 pod-reader,针对 pod 具备 get、list、watch
kubectl create role pod-reader --verb=get,list,watch --resource=pods -n auth
# 2. 赋予 fb 用户 auth 命名空间角色 pod-reader
kubectl create rolebinding fb-pod-reader -n auth --role=pod-reader --user=fb
# 3. 管理员身份在 auth 命名空间创建 deployment
kubectl create deployment web --image=hub.laoma.cloud/library/httpd -n auth
# 4. 回收 fb 用户集群管理员角色 cluster-admin(如果存在)
kubectl delete clusterrolebinding fb-admin
# 5. fb 用户验证:查看 deployment 和 pod(deployment 应被拒绝,pod 可查看)
kubectl get pod -n auth
kubectl get deployments -n auth # Forbidden
# 6. 清理资源
kubectl delete rolebinding fb-pod-reader -n auth
kubectl delete role pod-reader -n auth
kubectl delete deployment web -n auth
实践 2:使用现有集群角色(验证"限定命名空间无效")
bash
# 1. 赋予 fb 用户集群角色 admin,并「试图」限定在 auth 命名空间
# 注意:这里用的是 clusterrolebinding(集群级绑定)
kubectl create clusterrolebinding fb-admin --clusterrole=admin --user=fb
# 即便你手动给这个 ClusterRoleBinding 加上 namespace: auth 字段,
# 它仍然是集群级对象,不会因此被限制在 auth 内。
# 2. fb 用户在 auth 命名空间:创建、查看和删除 deployment → 成功
kubectl create deployment web1 --image=hub.laoma.cloud/library/httpd -n auth
kubectl get deployments -n auth
kubectl delete deployment web1 -n auth
# 3. fb 用户在 default 命名空间:创建、查看和删除 deployment → 同样成功
kubectl create deployment web2 --image=hub.laoma.cloud/library/httpd -n default
kubectl get deployments -n default
kubectl delete deployment web2 -n default
# 4. 回收权限
kubectl delete clusterrolebinding fb-admin
结论(实验要点) :用 ClusterRoleBinding 绑定集群角色时,"限定某个命名空间"是没有意义的 ------ClusterRoleBinding 本身是集群级对象,不归属任何 namespace,权限仍然针对集群级别、所有命名空间生效。
若真想把权限限制在单个命名空间,正确做法是:用 ClusterRole + RoleBinding(如上文 3.8 表所示),而不是给 ClusterRoleBinding 加 namespace 字段。
生产上要特别小心
cluster-admin的 ClusterRoleBinding ------ 一次绑定,全集群裸奔。
实践 3:自定义集群角色
bash
# 1. 创建 clusterrole,名称 cluster-pod-deploy-reader,能够查看 pod 和 deployment
# 注意:deployments 属于 apps 组,必须写 deployments.apps
kubectl create clusterrole cluster-pod-deploy-reader \
--verb=get,list,watch --resource=pods,deployments.apps
# 验证生成的规则
kubectl describe clusterrole cluster-pod-deploy-reader
# 2. 赋予 fb 用户(ClusterRoleBinding 全局生效)
kubectl create clusterrolebinding fb-pod-deploy-reader \
--clusterrole=cluster-pod-deploy-reader --user=fb
# 3. fb 用户在 auth 命名空间查看 pod、deployment → 成功
# 4. fb 用户在 kube-system 命名空间查看 pod、deployment → 成功
kubectl get pod,deploy -n auth
kubectl get pod,deploy -n kube-system
# 5. 删除集群角色绑定和集群角色
kubectl delete clusterrolebinding fb-pod-deploy-reader
kubectl delete clusterrole cluster-pod-deploy-reader
3.9 ServiceAccount 服务账户
概述
ServiceAccount(SA) 是 Pod 中进程访问 K8s API 的身份。给 SA 授予角色,使用该 SA 运行的 Pod 中的进程就具备相应权限。
每个 namespace 都有一个名为 default 的 SA,未显式指定时 Pod 都以它运行:
bash
root@master30:~# kubectl get sa
NAME SECRETS AGE
default 1 53d
# 创建一个 pod,验证 Service Account 信息
root@master30:~# kubectl run web --image=hub.laoma.cloud/library/httpd --image-pull-policy=IfNotPresent
root@master30:~# kubectl get pod web -o yaml | grep serviceAccount
serviceAccount: default
serviceAccountName: default
- serviceAccountToken:
服务账号 vs 用户账号
| 类别 | 用户账号(User) | 服务账号(ServiceAccount) |
|---|---|---|
| 使用主体 | 针对人 | 针对 Pod,Pod 中的应用通过 SA 访问集群 |
| 范围 | 集群范围,名称必须唯一 | 命名空间范围,不同 ns 可有同名 SA |
| 轻量程度 | 重量级,通过认证模块配置 | 轻量级,按需创建,便于遵循最小权限原则 |
服务账号令牌卷机制(K8s 1.24+ 重点)
Pod 调度到节点、kubelet 启动容器前:
- 根据
spec.serviceAccountName在当前命名空间查找 SA; - 获取该 SA 对应的令牌 ------ 1.24+ 默认不再自动创建永久 Secret,改用 Bound Service Account Token(短期轮换令牌);
- 将 token、ca.crt、namespace 以 投射卷(Projected Volume) 形式自动挂载到容器内默认路径:
/var/run/secrets/kubernetes.io/serviceaccount/
bash
root@master30:~# kubectl get pod web -o yaml
yaml
...
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-sjffr
readOnly: true
...
volumes:
- name: kube-api-access-sjffr
projected:
sources:
- serviceAccountToken:
path: token # 必须与应用所预期的路径匹配
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
三个数据源详解:
| 数据源 | 作用 |
|---|---|
serviceAccountToken |
kubelet 通过 TokenRequest API 获取的有时间限制的令牌 。Pod 删除或生命周期(默认 1 小时)结束后过期;绑定到特定 Pod,audience 与 apiserver 匹配。取代了旧的"不过期的 Secret 令牌"机制 |
configMap |
CA 证书数据,Pod 用它验证自己连的是真正的 apiserver,而非中间人 |
downwardAPI |
将 Pod 所在 namespace 名称暴露给容器内应用 |
容器内验证:
bash
root@master30:~# kubectl exec -it web -- bash
root@web:/usr/local/apache2# ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt namespace token
root@web:/usr/local/apache2# cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsI......
root@web:/usr/local/apache2# cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
auth
Pod 内应用(client-go / kubectl)访问 apiserver 的鉴权流程:
text
1. 读取容器内 /var/run/secrets/kubernetes.io/serviceaccount/token
2. HTTP 请求头带上:Authorization: Bearer <token>
3. 请求发送给 apiserver
4. apiserver 验证 Token 合法性,解析出对应的 ServiceAccount(namespace + SA 名称)
5. apiserver 查找所有绑定到此 SA 的 RoleBinding / ClusterRoleBinding
6. 根据 RBAC 规则判断:是否允许本次操作
7. 返回 允许 / 拒绝
SA 管理
bash
# 创建 SA
root@master30:~# kubectl create sa sa1
serviceaccount/sa1 created
root@master30:~# kubectl get sa sa1
NAME SECRETS AGE
sa1 1 9s
# 授权:注意 --serviceaccount 格式为 namespace:ServiceAccountName
root@master30:~# kubectl create clusterrolebinding auth-sa1-cluster-admin \
--clusterrole=cluster-admin --serviceaccount=auth:sa1
clusterrolebinding.rbac.authorization.k8s.io/auth-sa1-cluster-admin created
Pod 使用指定 SA:
bash
root@master30:~# kubectl delete pod web
root@master30:~# kubectl run web --image=hub.laoma.cloud/library/httpd \
--image-pull-policy=IfNotPresent --dry-run=client -o yaml > web.yaml
root@master30:~# vim web.yaml
yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: web
name: web
spec:
containers:
- image: httpd
imagePullPolicy: IfNotPresent
name: web
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
# ===== 添加以下任一记录(推荐写 serviceAccountName)=====
serviceAccount: sa1
serviceAccountName: sa1
status: {}
bash
root@master30:~# kubectl apply -f web.yaml
pod/web created
root@master30:~# kubectl get pod web -o yaml
......
spec:
......
serviceAccount: sa1
serviceAccountName: sa1
⚠️ 重要安全提醒 :虽然 pod/web 中的 httpd 进程现在持有
clusterrole/cluster-admin权限,但它只是提供 httpd 服务,不会主动做别的操作。然而,一旦该容器被攻破,攻击者就能直接用挂载的 token 接管整个集群。生产原则:永远遵循最小权限,不要图省事给 SA 绑
cluster-admin。
bash
# 回收权限
root@master30:~# kubectl delete clusterrolebindings auth-sa1-cluster-admin
clusterrolebinding.rbac.authorization.k8s.io "auth-sa1-cluster-admin" deleted
典型使用案例:集群 Dashboard(如 Kubernetes Dashboard、Kuboard)中的 Pod 使用服务账户来管理集群。
3.10 手动管理服务账号 Token
- v1.22 之前 :自动创建令牌 Secret,并挂载到运行的 Pod 中(永久有效,安全风险高)
- v1.28 及近期版本 :使用 TokenRequest API 直接获取 API 凭据,并通过投射卷挂载。令牌具有绑定的生命周期,Pod 删除时自动失效
创建临时令牌(推荐):
bash
root@master30:~# kubectl create sa sa1
root@master30:~# kubectl create token sa1
eyJhbGciOiJSUzI1NiIsImt......
# 可指定有效期
root@master30:~# kubectl create token sa1 --duration=3600s
注意 :服务账号属性中看不到关联的 token(这是设计使然):
bash
root@master30:~# kubectl get sa sa1 -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: "2023-11-01T04:05:03Z"
name: sa1
namespace: fb
resourceVersion: "11548"
uid: 4944f75b-43ae-43fc-abfd-f2d20b7375f1
创建永久令牌(不推荐,仅兼容旧场景):
bash
root@master30:~# vim sa1-token.yaml
yaml
apiVersion: v1
kind: Secret
metadata:
name: sa1-secret
annotations:
kubernetes.io/service-account.name: sa1
type: kubernetes.io/service-account-token
bash
root@master30:~# kubectl apply -f sa1-token.yaml
secret/sa1-secret created
# 查看 token
root@master30:~# kubectl get secret sa1-secret -o jsonpath='{.data.token}' | base64 -d
自动清理:删除 SA 时,K8s 控制面会自动清理与之关联的长期令牌 Secret。
bash
root@master30:~# kubectl delete sa sa1
serviceaccount "sa1" deleted
root@master30:~# kubectl get secrets
No resources found in fb namespace.
3.11 思考:K8s 为何不做用户管理?
问题 :Kubernetes 采用 RBAC 模型管理用户(User)和用户组(Group)权限,但集群中找不到真正的用户和用户组信息 ------甚至没有 User、Group 这两种 Resource。通过 API Server 根本无法列出可信的 User 列表与 Group 列表。换句话说,K8s 并没有提供用户管理和身份认证功能(ServiceAccount 除外),所有用户信息都依赖外部用户管理系统存储。
解答 :这非常符合 UNIX 设计哲学 ------ Do One Thing and Do It Well(做一件事,并把它做好)。
K8s 只专注于应用编排,其他功能一律提供接口集成。 除了认证和鉴权,网络(CNI)、存储(CSI)、运行时(CRI)都是同样的思路。
好处显而易见:
- 用户账户信息与 K8s 集群松耦合;
- 便于集成企业已有的身份认证系统:AD、LDAP、Keycloak、OIDC 等;
- K8s 核心保持精简,不被"用户管理"这类非核心需求拖累。
环境清理
bash
root@master30:~# kubectl delete ns auth
四、Dashboard 可视化管理
4.1 Kuboard
简介
Kuboard 是一款专为 Kubernetes 设计的免费管理界面,兼容 K8s 1.13 及以上版本。与官方 Dashboard 相比,主要区别在于:
- 从微服务参考架构视角组织界面,更贴近业务分层;
- 无需手工编写 YAML,大幅降低 K8s 使用门槛;
- 支持导出/导入整个微服务架构的部署信息;
- 提供内建监控套件、日志套件(成熟度较高)。
核心特点
| 特性 | 说明 |
|---|---|
| 多种认证方式 | 内建用户库、GitLab/GitHub 单点登录、LDAP 用户库,避免分发 SA Token 的麻烦 |
| 多集群管理 | 多集群导入 + 按集群/命名空间分配权限 |
| 微服务分层展示 | 按经典微服务分层展示工作负载,可自定义命名空间布局 |
| 工作负载直观展示 | Deployment 历史版本、Pod 列表、事件、容器信息同屏呈现 |
| 图形化编辑 | 支持 Node、Deployment、StatefulSet、DaemonSet、Secret、ConfigMap、Service、Ingress、PVC、StorageClass、LimitRange、ResourceQuota、SA、Role、CRD 等 |
| 存储类型支持 | 对接 NFS、CephFS,支持扩容与快照 |
| 丰富互操作 | Top Nodes/Pods、容器日志与终端、容器文件浏览器(上传/下载)、KuboardProxy |
| 套件扩展 | 资源监控(Prometheus + Grafana)、日志聚合(Loki + Promtail)、存储卷浏览器 |
| 告警配置 | 邮件/微信告警、告警路由、告警规则 |
| 操作审计 | 审计界面与 API 操作,支持自定义审计规则 |
在线演示 :https://demo.kuboard.cn (用户
demo,密码demo123,只读权限)
版本选择
| 版本 | 说明 |
|---|---|
| Kuboard v2.x | 支持单集群管理 |
| Kuboard v3.x | 支持多集群管理,支持 amd64 与 arm64(armv8) 架构 |
本文实验环境 K8s 版本 1.32.0,选用 Kuboard v3.x。
安装:以独立容器方式运行(推荐)
推荐理由:结构更清晰(Kuboard 作为多集群管理界面应独立于任何集群之外)、认证方式独立、排错更简单。
bash
# 创建数据目录
root@master30:~# mkdir /kuboard-data
# 创建容器(10.1.8.30 为物理机 IP)
root@master30:~# nerdctl run -d --name=kuboard \
-p 80:80/tcp \
-p 10081:10081/tcp \
-e KUBOARD_ENDPOINT="http://10.1.8.30:80" \
-e KUBOARD_AGENT_SERVER_TCP_PORT="10081" \
-v /kuboard-data:/data \
eipwork/kuboard:v3
若使用 Docker,将
nerdctl替换为docker即可。
卸载:
bash
root@master30:~# nerdctl container rm kuboard --force
root@master30:~# rm -fr /kuboard-data
安装:以 static pod 方式运行(自学)
bash
# 在 master 节点上执行
root@master30:~# curl -fsSL https://addons.kuboard.cn/kuboard/kuboard-static-pod.sh -o kuboard.sh
root@master30:~# sh kuboard.sh
current ip address is 10.1.8.30
create file /root/kuboard-sa.yaml
...
restart kubelet
# 验证
root@master30:~# kubectl get all -n kuboard
NAME READY STATUS RESTARTS AGE
pod/kuboard-v3-laoma30.redhat.fun 1/1 Running 0 45s
卸载:
bash
root@master30:~# rm -f /etc/kubernetes/manifests/kuboard.yaml
root@master30:~# rm -fr /usr/share/kuboard
root@master30:~# kubectl delete ns kuboard
访问与导入集群
浏览器访问 http://<your-host-ip>:80
- 用户名:
admin - 密码:
Kuboard123
导入集群步骤:
- 点击「添加集群」;
- 凭据方式选择
.kubeconfig; - 将控制节点
~/.kube/config的内容复制到文本框中导入; - 选择访问集群时使用的身份:ServiceAccount
kuboard-admin。
4.2 Kubernetes Dashboard
官方 Web UI,可部署容器化应用、诊断应用问题、管理集群资源、查看运行中的应用程序、创建修改资源、展示集群错误、伸缩 Deployment、执行滚动更新、重启 Pod 等。
部署
bash
# 方式一:下载官方资源清单
root@master30:~# wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.6.1/aio/deploy/recommended.yaml
# 查看镜像(国内拉取困难)
root@master30:~# grep image: recommended.yaml
image: kubernetesui/dashboard:v2.6.1
image: kubernetesui/metrics-scraper:v1.0.8
# 方式二:使用内网镜像仓库版本(实验环境推荐)
root@master30:~# wget -O recommended.yaml \
http://192.168.50.200/class/course-materials/softwares/stage03/kubernetes-dashboard-recommended-v2.6.1.yaml
root@master30:~# grep image: recommended.yaml
image: hub.laoma.cloud/kubernetesui/dashboard:v2.6.1
image: hub.laoma.cloud/kubernetesui/metrics-scraper:v1.0.8
# 执行部署
root@master30:~# kubectl apply -f recommended.yaml
验证
bash
root@master30:~# kubens
default
kube-node-lease
kube-public
kube-system
kubernetes-dashboard
root@master30:~# kubens kubernetes-dashboard
root@master30:~# kubectl get deployments.apps
NAME READY UP-TO-DATE AVAILABLE AGE
dashboard-metrics-scraper 1/1 1 1 2m25s
kubernetes-dashboard 1/1 1 1 2m25s
root@master30:~# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-dashboard-metrics-scraper ClusterIP 10.98.116.192 <none> 8000/TCP 3m54s
kubernetes-dashboard-web ClusterIP 10.107.63.216 <none> 8000/TCP 3m54s
root@master30:~# kubectl get serviceaccounts
NAME SECRETS AGE
default 1 23h
kubernetes-dashboard 1 23h
分析默认 RBAC 配置(这是理解 Dashboard 权限的关键):
bash
# 创建了 clusterrolebindings/kubernetes-dashboard
root@master30:~# kubectl get clusterrolebindings.rbac.authorization.k8s.io | grep dashboard
kubernetes-dashboard ClusterRole/kubernetes-dashboard 23h
# sa/kubernetes-dashboard 绑定了 ClusterRole/kubernetes-dashboard
root@master30:~# kubectl describe clusterrolebindings.rbac.authorization.k8s.io kubernetes-dashboard
Name: kubernetes-dashboard
Role:
Kind: ClusterRole
Name: kubernetes-dashboard
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount kubernetes-dashboard kubernetes-dashboard
# ClusterRole/kubernetes-dashboard 权限极小!
root@master30:~# kubectl describe clusterroles kubernetes-dashboard
Name: kubernetes-dashboard
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
nodes.metrics.k8s.io [] [] [get list watch]
pods.metrics.k8s.io [] [] [get list watch]
# deployments.apps/kubernetes-dashboard 以 sa/kubernetes-dashboard 身份运行
root@master30:~# kubectl get deployments.apps kubernetes-dashboard -o yaml | grep ' serviceAccount'
serviceAccount: kubernetes-dashboard
serviceAccountName: kubernetes-dashboard
结论 :默认部署的 Dashboard 只能看 metrics,什么都干不了。这是官方有意的安全设计(最小权限)。
修改 Service 为 NodePort
bash
root@master30:~# kubectl edit svc kubernetes-dashboard
spec:
......
type: NodePort # 由 ClusterIP 改为 NodePort
root@master30:~# kubectl get svc kubernetes-dashboard
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-dashboard NodePort 10.111.32.116 <none> 443:30518/TCP 4m45s
备选方案:使用 Ingress(需开启 SSL 透传)
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-dashboard
namespace: kubernetes-dashboard
annotations:
# 关键:透传 TLS,Ingress 不解密
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
ingressClassName: nginx
rules:
- host: dashboard.laoma.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kubernetes-dashboard
port:
number: 443
客户端将 dashboard.laoma.cloud 解析到 Ingress 服务地址(实验环境 10.1.8.40),即可访问 https://dashboard.laoma.cloud。
访问 Dashboard(新版本:只支持 Bearer Token)
浏览器访问 https://10.1.8.30:30518(忽略证书告警)。
新版 Dashboard 只支持 Bearer Token 登录。先看看默认账号有多小权限:
bash
# Kubernetes Dashboard 以 deployment 形式运行
root@master30:~# kubectl get deployments.apps
NAME READY UP-TO-DATE AVAILABLE AGE
dashboard-metrics-scraper 1/1 1 1 146m
kubernetes-dashboard 1/1 1 1 146m
# 以 serviceAccount: kubernetes-dashboard 身份运行
root@master30:~# kubectl get deployments.apps kubernetes-dashboard -o yaml | grep serviceAc
serviceAccount: kubernetes-dashboard
serviceAccountName: kubernetes-dashboard
# sa/kubernetes-dashboard 绑定了 Role/kubernetes-dashboard
root@master30:~# kubectl get rolebindings.rbac.authorization.k8s.io
NAME ROLE AGE
kubernetes-dashboard Role/kubernetes-dashboard 147m
# Role/kubernetes-dashboard 仅具备这些能力(只够 Dashboard 自身运行)
root@master30:~# kubectl describe role kubernetes-dashboard
Name: kubernetes-dashboard
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
secrets [] [kubernetes-dashboard-certs] [get update delete]
secrets [] [kubernetes-dashboard-csrf] [get update delete]
secrets [] [kubernetes-dashboard-key-holder] [get update delete]
configmaps [] [kubernetes-dashboard-settings] [get update]
services/proxy [] [dashboard-metrics-scraper] [get]
services/proxy [] [heapster] [get]
services [] [dashboard-metrics-scraper] [proxy]
services [] [heapster] [proxy]
创建登录 Token:
bash
root@master30:~# kubectl -n kubernetes-dashboard create token kubernetes-dashboard
eyJhbGciOiJSUzI1NiIsImtpZCI6Im9jekNIUUFRZUpKYzA3VXdmRHJvT3ZhaU1fMFNxVkh6NEJZa2lyUnhaaXcifQ...
用这个 Token 登录后,你会发现看不到 Pods ------ 因为权限只有 metrics。
授予集群管理员权限(实验环境做法):
bash
root@master30:~# kubectl create clusterrolebinding kubernetes-dashboard-cluster-admin \
--clusterrole cluster-admin \
--serviceaccount kubernetes-dashboard:kubernetes-dashboard
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard-cluster-admin created
重新创建 Token 登录后,即可看到全部资源。
⚠️ 两点提醒:
- Token 默认有效期 1 小时 (
kubectl create token默认时长),过期需重新生成。可用--duration指定,如kubectl -n kubernetes-dashboard create token kubernetes-dashboard --duration=8760h。- 生产环境不要 直接绑
cluster-admin,应创建只读或受限 ClusterRole,按需授权。
旧版本获取 Token
旧版本默认已为 sa/kubernetes-dashboard 创建了长期 Secret:
bash
# 查看 SA 关联的 token
root@master30:~# kubectl describe sa kubernetes-dashboard
Name: kubernetes-dashboard
Namespace: kubernetes-dashboard
Mountable secrets: kubernetes-dashboard-token-hm26j
Tokens: kubernetes-dashboard-token-hm26j
# 获取 token 内容
root@master30:~# kubectl describe secrets kubernetes-dashboard-token-hm26j
Name: kubernetes-dashboard-token-hm26j
Namespace: kubernetes-dashboard
Annotations: kubernetes.io/service-account.name: kubernetes-dashboard
kubernetes.io/service-account.uid: d4b7d7ec-a76b-46c5-9dc5-629217cd07ba
Type: kubernetes.io/service-account-token
Data
====
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IkNVVmdmMGNUckVGZFFPUVJ0dTkxZGtEc2hoOTlGZ2ktWjI2RmtnRWI5dFEifQ...
ca.crt: 1066 bytes
namespace: 20 bytes
# 一条命令直接取出
root@master30:~# kubectl -n kubernetes-dashboard get secret kubernetes-dashboard-token-hm26j \
-o jsonpath='{.data.token}' | base64 -d
五、面试速查清单
健康检查
| 问题 | 答案 |
|---|---|
| liveness 失败会怎样? | 重启容器(Pod 不变,容器重建) |
| readiness 失败会怎样? | 从 Endpoints 摘除 ,不重启 |
| startupProbe 的作用? | 保护慢启动容器,成功前 liveness/readiness 不执行 |
| httpGet 成功状态码范围? | 200 ≤ code < 400 |
| exec 成功条件? | 命令退出码为 0 |
| tcpSocket 成功条件? | 端口能建立连接(立即关闭也算健康) |
| Pod Running 但 READY 0/1? | readiness 未通过,检查探针路径/端口 |
| Pod 反复重启? | liveness 未通过,检查 initialDelaySeconds 是否太短 |
| 滚动更新导致全站不可用? | 缺 Health Check,新副本未就绪就被加入 Service |
认证与授权
| 问题 | 答案 |
|---|---|
| 认证失败 / 鉴权失败的状态码? | 401 / 403 |
| 三阶段顺序? | 认证 → 鉴权 → 准入控制(读操作不触发准入) |
| K8s 有 User 对象吗? | 没有,普通用户由外部系统提供,靠 X509 证书 CN 识别 |
| Role 与 ClusterRole 区别? | 前者 namespace 级,后者集群级 |
| ClusterRole + RoleBinding 效果? | 仅在该 namespace 生效 |
| ClusterRole + ClusterRoleBinding 效果? | 全集群生效 |
| 写 Deployment 规则时 apiGroups 填什么? | apps (填 "" 不生效) |
--user 与 --username 区别? |
前者是 RBAC 绑定用户,后者是 basic 认证用户名 |
| SA Token 挂载路径? | /var/run/secrets/kubernetes.io/serviceaccount/ |
| K8s 1.24+ SA Token 变化? | 不再自动创建永久 Secret,改用有时限的 Bound Token |
| 创建临时 Token 命令? | kubectl create token <sa> |
| 为什么 K8s 不做用户管理? | Do One Thing and Do It Well,便于集成 AD/LDAP/OIDC |
Dashboard
| 问题 | 答案 |
|---|---|
| 新版 Dashboard 登录方式? | 仅 Bearer Token |
| 默认 Dashboard 权限为何看不到 Pod? | 只绑了 ClusterRole/kubernetes-dashboard(仅 metrics 权限) |
| 让它能看全集群的命令? | kubectl create clusterrolebinding ... --clusterrole cluster-admin --serviceaccount kubernetes-dashboard:kubernetes-dashboard |
| Kuboard v3 默认账密? | admin / Kuboard123 |
六、总结
本文覆盖了 K8s 运维中最常出问题、也最能体现专业度的三个主题:
- 健康检查 ------ 让集群具备"自愈"与"优雅上下线"能力,是滚动更新零中断的基石;
- 认证授权 ------ 理解"认证管你是谁、鉴权管你能干什么",掌握 X509 + RBAC 的完整链路;
- 可视化管理 ------ Kuboard 与 Dashboard 双方案,快速上手日常运维。
三条生产建议:
- liveness 探针要轻量且保守 (不要因为依赖抖动就重启容器),readiness 探针可以激进一些;
- RBAC 永远遵循最小权限原则 ,拒绝图省事的
cluster-admin; - SA Token 优先使用短期 TokenRequest,避免创建永久 Secret。