K8s 资源管控与权限实战:HPA 弹性伸缩、资源配额、探针、RBAC 权限全解析

本手册由 master30_2026-08-13_10_13_37.log(10:13–17:04)+ worker31_2026-08-13_15_27_14.log(15:27–17:04)合并整理而成,在 kubeadm v1.30.2 集群(master30 + worker31/32,运行第 8 天)上完成。
四条主线:① HPA 内存指标弹性伸缩 → ② 资源管理(ResourceQuota 配额 + LimitRange 默认限额)→ ③ 健康检查探针(liveness / readiness 三种方式)→ ④ 认证授权(证书签发 + kubeconfig + RBAC)。命令提示符保留真实时间戳。

📑 目录

# 章节 核心内容
整体框架与实验总览 实验流程图、节点环境、主题速览
HPA 内存指标自动扩缩容 100MB 大文件压测、60%→20%、2→3 副本
ResourceQuota 资源配额 对象配额、计算资源配额、patch 调整、OOMKilled
LimitRange 默认限额 max/min/default/defaultRequest 自动注入
探针实战:liveness(httpGet) 删首页文件、RESTARTS 自愈
探针实战:liveness(exec / tcpSocket) busybox /tmp/healthy、80 端口探测
探针实战:readiness 就绪探针 0/1 摘除、endpoints、curl 轮询验证
认证:创建用户与证书签发 genrsa/req、CA 签名、openssl 验证
认证:kubeconfig 配置 set-cluster/credentials/context、server 为空修复
授权:RBAC 角色与绑定 clusterrolebinding、Role、RoleBinding
十一 环境清理与恢复 命名空间与资源回收
十二 常见错误与排查(重点) 本次实验全部报错:原因 + 修复
十三 命令速查表与小结 HPA/配额/探针/证书/RBAC 常用命令

一、整体框架与实验总览

1.1 实验流程图

集群运行第 8 天
master30 + worker31/32

HPA 内存伸缩
100MB 大文件 + ab 压测
指标 60% 改 20%

资源管理
ResourceQuota 配额
LimitRange 默认限额

健康检查探针
liveness httpGet/exec/tcpSocket
readiness 就绪摘除

认证授权
证书签发 + kubeconfig
RBAC Role / RoleBinding

错误复盘 + 命令速查

1.2 节点环境

节点 主机名 IP 角色
master30 master30.ningcode.cn 10.1.8.30 控制平面
worker31 worker31.ningcode.cn 10.1.8.31 工作节点
worker32 worker32.ningcode.cn 10.1.8.32 工作节点
  • Pod 网段 10.224.0.0/16;Service 网段 10.96.0.0/12;API Server 地址 https://master30.ningcode.cn:6443
  • 私有镜像仓库 hub.laoma.cloud(nginx/httpd/busybox 均已缓存,IfNotPresent 秒起);压测镜像 hub.laoma.cloud/progrium/stress
  • 网络插件 Calico;metrics-server 已就绪(kubectl top 可用);本次认证实验用 worker31 模拟外部客户端

1.3 主题速览

主题 核心知识点 时间范围 节点
HPA 内存伸缩 memory Utilization、ab 压测 100MB 文件、2→3 副本 10:13–10:41 master30
ResourceQuota pods/services 对象配额、requests/limits 计算资源配额、patch 11:24–11:37 master30
LimitRange max/min/default/defaultRequest 自动注入 11:37–11:45 master30
liveness 探针 httpGet / exec / tcpSocket、RESTARTS 自愈 13:55–14:47 master30
readiness 探针 0/1 摘除、endpoints、curl 轮询 14:02–14:07 master30
认证授权 CSR 签发、kubeconfig、cluster-admin / Role / RoleBinding 15:25–17:04 master30 + worker31

↑ 回到目录


二、HPA 内存指标自动扩缩容

本次实验目标:用 HPA 按内存使用率自动扩缩容。先做一次 60% 目标值,因负载压不上去(环境限制),改为 20% 后成功从 2 副本弹到 3 副本。

2.1 准备:删除旧命名空间、确认集群状态

root@master30 ~ 10:13:32# kubectl delete ns scheduler
namespace "scheduler" deleted

# 查看集群 Service(ingress-nginx 的 EXTERNAL-IP 仍为 <pending>,本次实验不涉及)
root@master30 ~ 10:14:20# kubectl get svc -A
NAMESPACE       NAME                                 TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
default         kubernetes                           ClusterIP      10.96.0.1        <none>        443/TCP                      7d20h
ingress-nginx   ingress-nginx-controller             LoadBalancer   10.97.52.107     <pending>     80:32455/TCP,443:30093/TCP   41h
kube-system     kube-dns                             ClusterIP      10.96.0.10       <none>        53/UDP,53/TCP,9153/TCP       7d20h
kube-system     metrics-server                       ClusterIP      10.105.250.124   <none>        443/TCP                      17h

2.2 制造 100MB 测试文件(hostPath 挂载)

下面的 deployment.yamlhostPath 把节点的 /www 目录挂到 nginx 的 /usr/share/nginx/html,压测时下载 /big.img 大文件制造内存压力。/www 的创建命令应在运行 Pod 的工作节点上执行(本次在 master30 上直接执行,若复现请先确认 Pod 调度到哪个节点)。

root@master30 ~ 10:14:24# mkdir /www
root@master30 ~ 10:14:38# dd if=/dev/zero of=/www/big.img bs=1M count=100
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0.218516 s, 480 MB/s

# 实验结束后删除大文件(注意:输入 rm -rf / 后按 Tab 会列出根目录,极易误删,务必小心)
root@master30 ~ 10:15:45# rm -rf /www/big.img

2.3 Deployment:nginx + hostPath + 资源限额

root@master30 ~ 10:15:57# vim deployment.yaml
root@master30 ~ 10:16:51# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: big-file
          mountPath: /usr/share/nginx/html
        resources:
          limits:
            cpu: 100m
            memory: 200Mi
      volumes:
      - name: big-file
        hostPath:
          path: /www

第一次 apply 时报 namespaces "web" not found——当前 context 指向 web 命名空间但该命名空间还没创建,先建命名空间再部署即可:

root@master30 ~ 10:19:06# kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
*         kubernetes-admin@kubernetes   kubernetes   kubernetes-admin   web

root@master30 ~ 10:19:14# kubectl create namespace web
namespace/web created

root@master30 ~ 10:19:14# kubectl apply -f deployment.yaml
deployment.apps/web created

↑ 回到目录


2.4 创建 HPA:内存指标 60%

root@master30 ~ 10:19:46# vim hpa-mem.yaml
root@master30 ~ 10:20:03# kubectl apply -f hpa-mem.yaml
horizontalpodautoscaler.autoscaling/web created

root@master30 ~ 10:20:09# kubectl get hpa
NAME   REFERENCE        TARGETS                 MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   memory: <unknown>/60%   2         5         0          5s

root@master30 ~ 10:20:30# kubectl get hpa
NAME   REFERENCE        TARGETS          MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   memory: 7%/60%   2         5         2          27s

第一次 hpa-mem.yaml 的指标为 memory 平均利用率 60%averageUtilization: 60,min 2 / max 5),指标采集需要十几秒,刚创建时显示 <unknown>,随后变成 7%/60%,副本数 2。

2.5 Service 暴露 + ab 压测

# 打开一个监控窗口(注释形式记录,未实际执行)
root@master30 ~ 10:20:48# # watch -n 4 'kubectl get hpa;echo;kubectl top pods'
# 上 MEM 压力,重用上面的 big.img
root@master30 ~ 10:20:52# # 上 MEM 压力,重用上面的

root@master30 ~ 10:21:16# kubectl expose deployment web
service/web exposed

root@master30 ~ 10:21:38# kubectl get svc
NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.105.68.225   <none>        80/TCP    2s

# 循环压测:100 并发反复下载 100MB 大文件
root@master30 ~ 10:21:40# while true ;do ab -n 300000 -c 100 http://10.105.68.225/big.img;sleep 1;done
This is ApacheBench, Version 2.3 <$Revision: 1903618 $>
......

ab 压测输出较长,这里省略。压了十几分钟后内存使用率一直上不去(节点资源有限),于是注释记录:“这里负载一直上不去,实验环境我们改一下指标”,把 HPA 目标从 60% 调到 20%。

2.6 调整指标为 20% 并重新部署

root@master30 ~ 10:34:02# vim hpa-mem.yaml
root@master30 ~ 10:34:18# cat hpa-mem.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web
spec:
  minReplicas: 2
  maxReplicas: 5
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  metrics:
  - resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 20
    type: Resource
# 删除旧的 deployment / hpa / service,重新 apply
root@master30 ~ 10:35:24# kubectl delete deployments.apps web
deployment.apps "web" deleted
root@master30 ~ 10:35:37# kubectl delete horizontalpodautoscalers.autoscaling web
horizontalpodautoscaler.autoscaling "web" deleted
root@master30 ~ 10:35:54# kubectl delete service web
service "web" deleted

root@master30 ~ 10:36:06# kubectl apply -f deployment.yaml
deployment.apps/web created
root@master30 ~ 10:36:14# kubectl apply -f hpa-mem.yaml
horizontalpodautoscaler.autoscaling/web created

root@master30 ~ 10:36:35# kubectl get hpa
NAME   REFERENCE        TARGETS          MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   memory: 1%/20%   2         5         2          20s

# 重新暴露 Service(注意 ClusterIP 会变)
root@master30 ~ 10:37:07# kubectl expose deployment web
service/web exposed
root@master30 ~ 10:37:24# kubectl get svc
NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.99.152.229   <none>        80/TCP    6s

↑ 回到目录


2.7 压测与扩缩容结果

# 继续用新 Service IP 压测
root@master30 ~ 10:37:26# while true ;do ab -n 300000 -c 100 http://10.99.152.229/big.img;sleep 1;done &
[1] 81943
This is ApacheBench, Version 2.3 <$Revision: 1903618 $>
......

# 监控窗口(watch)中看到的结果:
root@master30 ~ 10:40:57# : << END
Every 4.0s: kubectl get hpa;echo;kubectl top pods    master30.ningcode.cn: Thu Aug 13 10:40:43 2026

NAME   REFERENCE        TARGETS           MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   memory: 17%/20%   2         5         3          4m23s

NAME                  CPU(cores)   MEMORY(bytes)
web-88cff78bb-6f6vz   18m          34Mi
web-88cff78bb-wb9kd   19m          34Mi
web-88cff78bb-xrht4   21m          34Mi
END

root@master30 ~ 10:41:08# #环境配置有限,节点内存负载一直上不去但是能看到已经弹出来了一个pod

结论:目标 20% 生效后,HPA 从 min 2 副本自动弹出第 3 个 Pod(2→3)。环境内存有限,负载没法继续往上顶,但“按内存自动扩容”的机制已完整验证。压测结束后清理整个 web 命名空间:

root@master30 ~ 10:41:45# kubectl delete ns web
namespace "web" deleted

↑ 回到目录


三、ResourceQuota 资源配额

ResourceQuota 限制一个命名空间内可创建的资源总量(对象个数 + 计算资源总和)。本次分三步:对象数量配额 → 计算资源配额(requests/limits)→ 压测验证 limits 上限。

3.1 对象数量配额(pods / services / secrets / pvc)

root@master30 ~ 11:24:49# kubectl create ns quota
namespace/quota created
root@master30 ~ 11:24:54# kubectl config set-context --current --namespace quota
Context "kubernetes-admin@kubernetes" modified.

root@master30 ~ 11:26:26# kubectl create quota myquota --hard=pods=2,services=3,secrets=5,persistentvolumeclaims=10
resourcequota/myquota created

root@master30 ~ 11:26:38# kubectl get resourcequotas
NAME      AGE   REQUEST                                                                LIMIT
myquota   14s   persistentvolumeclaims: 0/10, pods: 0/2, secrets: 0/5, services: 0/3

root@master30 ~ 11:26:52# kubectl describe quota myquota
Name:                   myquota
Namespace:              quota
Resource                Used  Hard
--------                ----  ----
persistentvolumeclaims  0     10
pods                    0     2
secrets                 0     5
services                0     3

副本数超配额被拒:创建 --replicas 3 的 Deployment,只有 2 个 Pod 能起来,第 3 个被配额拦截:

root@master30 ~ 11:27:18# kubectl create deployment web --image=hub.laoma.cloud/library/nginx --replicas 3
deployment.apps/web created

root@master30 ~ 11:27:54# kubectl get all
NAME                       READY   STATUS    RESTARTS   AGE
pod/web-759dfd9847-vhhr4   1/1     Running   0          4s
pod/web-759dfd9847-zm74n   1/1     Running   0          4s

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   2/3     2            2           4s

# describe RS 查看事件:明确提示 exceeded quota
root@master30 ~ 11:27:58# kubectl describe rs web-759dfd9847
......
Events:
  Warning  FailedCreate  20s  replicaset-controller  Error creating: pods "web-759dfd9847-kks77" is forbidden: exceeded quota: myquota, requested: pods=1, used: pods=2, limited: pods=2
  ......

调大配额后恢复:用 kubectl patchpods 上限改成 10,再 scale 副本即可:

root@master30 ~ 11:28:14# kubectl patch resourcequotas myquota -p '{"spec":{"hard":{"pods":10}}}'
resourcequota/myquota patched

root@master30 ~ 11:28:55# kubectl scale rs web-759dfd9847 --replicas 3
replicaset.apps/web-759dfd9847 scaled

root@master30 ~ 11:29:11# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
web-759dfd9847-n8wtw   1/1     Running   0          1s
web-759dfd9847-vhhr4   1/1     Running   0          78s
web-759dfd9847-zm74n   1/1     Running   0          78s

# 清理
root@master30 ~ 11:29:12# kubectl delete deployments.apps web
deployment.apps "web" deleted
root@master30 ~ 11:29:18# kubectl delete resourcequotas myquota
resourcequota "myquota" deleted

↑ 回到目录


3.2 计算资源配额(requests / limits)

配额同时声明 CPU/内存的 requests 与 limits 总量(YAML 方式,效果与命令行一致):

root@master30 ~ 11:29:22# vim resourcequota.yaml
root@master30 ~ 11:32:18# cat resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: myquota
spec:
  hard:
    persistentvolumeclaims: "10"
    pods: "2"
    secrets: "5"
    services: "3"
    requests.cpu: 1000m
    requests.memory: "2048Mi"
    limits.cpu: 1000m
    limits.memory: "2048Mi"

不写资源的 Pod 被拒:配额要求命名空间内每个 Pod 必须显式声明计算资源:

root@master30 ~ 11:32:59# kubectl apply -f pod-without-quota.yaml
Error from server (Forbidden): error when creating "pod-without-quota.yaml": pods "web" is forbidden: failed quota: myquota: must specify limits.cpu for: web; limits.memory for: web; requests.cpu for: web; requests.memory for: web

3.3 只限制 requests:单 Pod 超上限被拒

把配额改成只限制 requests(requests.cpu: 1000mrequests.memory: 2048Mi),分别测试超限与合规:

root@master30 ~ 11:33:51# kubectl delete resourcequotas myquota
resourcequota "myquota" deleted

root@master30 ~ 11:34:11# cat resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: myquota
spec:
  hard:
    requests.cpu: 1000m
    requests.memory: "2048Mi"
# 测试-Request:pod-request-1 请求 2000m/4096Mi,超过配额总上限
root@master30 ~ 11:34:45# kubectl apply -f pod-request-1.yaml
Error from server (Forbidden): error when creating "pod-request-1.yaml": pods "web" is forbidden: exceeded quota: myquota, requested: requests.cpu=2,requests.memory=4Gi, used: requests.cpu=0,requests.memory=0, limited: requests.cpu=1,requests.memory=2Gi
root@master30 ~ 11:34:53# #此时超上限了

# pod-request-2 请求 200m/1024Mi,合规 → 创建成功
root@master30 ~ 11:35:34# kubectl apply -f pod-request-2.yaml
pod/web created

root@master30 ~ 11:36:02# kubectl describe resourcequotas myquota
Name:            myquota
Namespace:       quota
Resource         Used  Hard
--------         ----  ----
requests.cpu     200m  1
requests.memory  1Gi   2Gi

# 清理
root@master30 ~ 11:36:10# kubectl delete pod web
pod "web" deleted
root@master30 ~ 11:36:40# kubectl delete resourcequotas myquota
resourcequota "myquota" deleted

3.4 只限制 limits:stress 压测触顶(OOMKilled)

root@master30 ~ 11:36:46# #用stress 进行压力测试
root@master30 ~ 11:37:30# cat resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: myquota
spec:
  hard:
    limits.cpu: 1000m
    limits.memory: "2048Mi"

CPU 压测pod-limit-cpu.yaml,stress -c 1,与后面 pod-without-limits.yaml 同结构):

root@master30 ~ 11:38:09# kubectl apply -f pod-limit-cpu.yaml
pod/stress created

root@master30 ~ 11:38:18# kubectl top pods
NAME     CPU(cores)   MEMORY(bytes)
stress   201m         0Mi

root@master30 ~ 11:39:24# kubectl delete pod stress --force
pod "stress" force deleted

内存压测pod-limit-memory.yaml,stress 压内存超过 Pod limit):

root@master30 ~ 11:40:09# kubectl apply -f pod-limit-memory.yaml
pod/stress created

root@master30 ~ 11:40:20# kubectl get pods -w
NAME     READY   STATUS             RESTARTS     AGE
stress   0/1     CrashLoopBackOff   1 (4s ago)   8s
stress   1/1     Running            2 (16s ago)  20s
stress   0/1     OOMKilled          2 (17s ago)  21s
^C
root@master30 ~ 11:40:49# #发现pod状态为oomkilled,进行restart

内存超过 limit 后容器被 OOMKilled,kubelet 自动重启(CrashLoopBackOff 循环)。清理:

root@master30 ~ 11:41:04# kubectl delete pod stress --force
root@master30 ~ 11:41:13# kubectl delete resourcequotas myquota

↑ 回到目录


四、LimitRange 默认限额

LimitRange 为命名空间内未声明 resources 的 Pod/容器自动注入默认值,并约束 min/max 范围,与 ResourceQuota(总量)互补。

4.1 创建 LimitRange

root@master30 ~ 11:41:19# vim limits.yaml
root@master30 ~ 11:41:42# cat limits.yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: mylimit
spec:
  limits:
    - type: Container
      max:
        memory: 1024Mi
        cpu: 1
      min:
        memory: 128Mi
        cpu: 100m
      default:
        memory: 512Mi
        cpu: 500m
      defaultRequest:
        memory: 256Mi
        cpu: 200m
root@master30 ~ 11:41:51# kubectl get limitranges
NAME      CREATED AT
mylimit   2026-08-13T03:41:51Z

root@master30 ~ 11:41:56# kubectl describe limitranges mylimit
Name:       mylimit
Namespace:  quota
Type        Resource  Min    Max  Default Request  Default Limit  Max Limit/Request Ratio
----        --------  ---    ---  ---------------  -------------  -----------------------
Container   cpu       100m   1    200m             500m           -
Container   memory    128Mi  1Gi  256Mi            512Mi          -

↑ 回到目录


4.2 不写 resources 的 Pod 自动获得默认值

root@master30 ~ 11:42:09# vim pod-without-limits.yaml
root@master30 ~ 11:43:56# cat pod-without-limits.yaml
apiVersion: v1
kind: Pod
metadata:
  name: stress
spec:
  containers:
  - name: stress
    image: hub.laoma.cloud/progrium/stress
    imagePullPolicy: IfNotPresent
    args: ['-c','1']
root@master30 ~ 11:44:03# kubectl apply -f pod-without-limits.yaml
pod/stress created

root@master30 ~ 11:44:28# kubectl top pods
NAME     CPU(cores)   MEMORY(bytes)
stress   499m         0Mi

root@master30 ~ 11:44:29# #创建出来的pod的resources 与 limitranage 指定的相关默认值一致
root@master30 ~ 11:44:37# kubectl get pod stress -o yaml
......
    kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container stress; cpu, memory limit for container stress'
  ......
  containers:
  - args:
    - -c
    - "1"
    image: hub.laoma.cloud/progrium/stress
    imagePullPolicy: IfNotPresent
    name: stress
    resources:
      limits:
        cpu: 500m
        memory: 512Mi
      requests:
        cpu: 200m
        memory: 256Mi
  ......

关键验证:Pod 未写 resources,LimitRanger 自动注入了 default(limits cpu 500m / memory 512Mi)和 defaultRequest(requests cpu 200m / memory 256Mi),与 LimitRange 定义完全一致。清理:

root@master30 ~ 11:44:58# kubectl delete limitranges mylimit
limitrange "mylimit" deleted
root@master30 ~ 11:45:06# kubectl delete pod web --force
# 午休后继续:清理 stress 与 quota 命名空间
root@master30 ~ 13:34:41# kubectl delete ns quota
namespace "quota" deleted

↑ 回到目录


五、探针实战(一):liveness 存活探针 httpGet

存活探针失败时 kubelet 会重启容器(RESTARTS +1)。本实验用 httpd 镜像,删掉首页文件制造故障。

5.1 基线:正常访问与制造故障

root@master30 ~ 13:55:39# kubectl create ns health
namespace/health created
root@master30 ~ 13:55:48# kubectl config set-context --current --namespace health
Context "kubernetes-admin@kubernetes" modified.

root@master30 ~ 13:55:52# kubectl run web --image=hub.laoma.cloud/library/httpd --image-pull-policy=IfNotPresent
pod/web created

root@master30 ~ 13:56:31# kubectl describe pod web|grep IP
IP:               10.224.195.155

root@master30 ~ 13:56:44# curl 10.224.195.155
<html><body><h1>It works!</h1></body></html>

# 删除首页文件(注意 -- 是 kubectl exec 的参数分隔符,-rm / --rm 都是错误写法)
root@master30 ~ 13:57:43# kubectl exec web -- rm -f htdocs/index.html

root@master30 ~ 13:57:49# curl 10.224.195.155
<!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>

root@master30 ~ 13:58:06# kubectl delete pod web --force
pod "web" force deleted

没有探针时:删了首页文件 Pod 照常 Running(只是访问变成目录列表)。加上 liveness 探针后,容器会被自动重启修复。

↑ 回到目录


5.2 httpGet liveness 配置

root@master30 ~ 13:58:13# vim deploy-httpGet-liveness.yaml
root@master30 ~ 13:59:30# cat deploy-httpGet-liveness.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
          httpGet:
            path: /index.html
            # port填写实际web端口
            port: 80
            # scheme指定协议,HTTP或者HTTPS
            scheme: HTTP
root@master30 ~ 13:58:56# kubectl apply -f deploy-httpGet-liveness.yaml
deployment.apps/web created

root@master30 ~ 13:59:12# kubectl describe pod web-7dfcbbb5df-dx8jx |grep IP
IP:               10.224.83.170

root@master30 ~ 13:59:50# curl 10.224.83.170
<html><body><h1>It works!</h1></body></html>

5.3 制造故障:删除首页 → RESTARTS 变为 1

root@master30 ~ 14:00:20# kubectl exec web-7dfcbbb5df-dx8jx -- bash -c 'rm htdocs/index.html'

# 连续观察:RESTARTS 变为 1(容器已被 liveness 探针杀掉并重启)
root@master30 ~ 14:01:04# kubectl get pods
NAME                   READY   STATUS    RESTARTS     AGE
web-7dfcbbb5df-dx8jx   1/1     Running   1 (8s ago)   2m5s

# 容器重启后首页文件被镜像重新还原,再次访问恢复正常
root@master30 ~ 14:01:06# curl 10.224.83.170
<html><body><h1>It works!</h1></body></html>

root@master30 ~ 14:01:17# kubectl delete deployments.apps web
deployment.apps "web" deleted

探针配置说明:initialDelaySeconds: 5(启动 5s 后再探测)、periodSeconds: 5(每 5s 探测一次)、failureThreshold: 3(连续 3 次失败才重启)、timeoutSeconds: 10(单次探测超时)。

↑ 回到目录


六、探针实战(二):liveness exec 与 tcpSocket

6.1 exec 探针:httpd 检查首页文件存在

root@master30 ~ 14:07:33# vim deploy-exec-liveness.yaml
root@master30 ~ 14:36:48# cat deploy-exec-liveness.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
root@master30 ~ 14:36:53# kubectl apply -f deploy-exec-liveness.yaml
deployment.apps/web created

# 删首页 → 探针 cat 失败 → 容器重启
root@master30 ~ 14:37:07# kubectl exec web-546966967b-jgbz7 -- bash -c 'rm htdocs/index.html'
root@master30 ~ 14:37:30# kubectl get pod
NAME                   READY   STATUS    RESTARTS   AGE
web-546966967b-jgbz7   1/1     Running   0          31s

↑ 回到目录


6.2 exec 探针:busybox 定时销毁 /tmp/healthy

先导出 busybox 模板,再改造成“创建 /tmp/healthy → 10 秒后删除”的测试 Pod:

root@master30 ~ 14:37:35# kubectl run busybox --image=busybox --image-pull-policy=IfNotPresent -o yaml --dry-run=client > busybox.yaml
root@master30 ~ 14:38:36# vim deploy-exec-busybox.yaml
root@master30 ~ 14:41:48# cat deploy-exec-busybox.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: busybox
  name: busybox
spec:
  containers:
  - image: busybox
    imagePullPolicy: IfNotPresent
    name: busybox

    # 添加args参数:保活并周期性地让探针失败
    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
root@master30 ~ 14:39:12# kubectl apply -f deploy-exec-busybox.yaml
pod/busybox created

第一次 apply 的版本没写 args,busybox 启动即退出,Pod 一直 CrashLoopBackOff;补上 args(touch healthy → 10s 后删除)后正常。在另一个终端观察事件:

root@master30 ~ 14:42:24# kubectl get pods
NAME                   READY   STATUS    RESTARTS        AGE
busybox                1/1     Running   0               3s
web-546966967b-jgbz7   1/1     Running   1 (4m42s ago)   5m23s

root@master30 ~ 14:43:04# kubectl describe pod busybox |tail -10
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  2m39s                default-scheduler  Successfully assigned health/busybox to worker31.ningcode.cn
  Normal   Pulled     49s (x3 over 2m39s)  kubelet            Container image "busybox" already present on machine
  Normal   Created    49s (x3 over 2m39s)  kubelet            Created container busybox
  Normal   Started    49s (x3 over 2m39s)  kubelet            Started container busybox
  Warning  Unhealthy  24s (x9 over 2m24s)  kubelet            Liveness probe failed: ls: /tmp/healthy: No such file or directory
  Normal   Killing    24s (x3 over 2m14s)  kubelet            Container busybox failed liveness probe, will be restarted

root@master30 ~ 14:45:30# kubectl delete pod busybox --force
pod "busybox" force deleted

6.3 tcpSocket 探针:检查 80 端口

root@master30 ~ 14:45:12# vim deploy-tcpSocket-liveness.yaml
root@master30 ~ 14:46:01# cat deploy-tcpSocket-liveness.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
root@master30 ~ 14:46:41# kubectl apply -f deploy-tcpSocket-liveness.yaml
deployment.apps/web configured

root@master30 ~ 14:47:03# kubectl get all
NAME                       READY   STATUS    RESTARTS   AGE
pod/web-79869c84fd-8ghzf   1/1     Running   0          4s
......

root@master30 ~ 14:47:07# kubectl describe pods web-79869c84fd-8ghzf
......
  httpd:
    Container ID:   containerd://e6c1939c2d2be10c410945214b5d4422fd1e51d89cde7199b1ac07800b7854d5
    Image:          hub.laoma.cloud/library/httpd
    State:          Running
      Started:      Thu, 13 Aug 2026 14:47:03 +0800
    Ready:          True
    Restart Count:  0
    Liveness:       tcp-socket :80 delay=5s timeout=10s period=5s #success=1 #failure=3
  ......

describe 输出里能看到探针定义:Liveness: tcp-socket :80 delay=5s timeout=10s period=5s #success=1 #failure=3

↑ 回到目录


七、探针实战(三):readiness 就绪探针

readiness 与 liveness 的区别:readiness 失败只把 Pod 标记为 NotReady(0/1)并从 Service Endpoints 摘除,不会重启容器;liveness 失败会重启容器。本实验用 3 副本 + Service 验证“流量只进 Ready Pod”。

7.1 配置 readiness(httpGet)并暴露 Service

root@master30 ~ 14:02:31# vim deploy-httpGet-readiness.yaml
root@master30 ~ 14:03:00# kubectl apply -f deploy-httpGet-readiness.yaml
deployment.apps/web created

root@master30 ~ 14:03:10# kubectl expose deployment web --port=80 --target-port=80
service/web exposed

root@master30 ~ 14:03:31# kubectl get svc
NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.108.251.21   <none>        80/TCP    5s

root@master30 ~ 14:03:36# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
web-5d6964b9f5-6kwvt   1/1     Running   0          33s
web-5d6964b9f5-9wj4q   1/1     Running   0          33s
web-5d6964b9f5-blz4z   1/1     Running   0          33s

deploy-httpGet-readiness.yaml 与第 5 章的 httpGet liveness 结构一致,只是把 livenessProbe 换成 readinessProbe(path /index.html、port 80),并把副本数改为 3(日志中该文件未 cat,按同结构整理)。

7.2 给每个 Pod 写入各自的首页内容

# 把 pod 名写进各自的 index.html,用于区分流量打到了谁
root@master30 ~ 14:03:43# for pod in $(kubectl get pods -o name|awk -F / '{print $2}'); do kubectl exec $pod -- bash -c "echo $pod > htdocs/index.html"; done

root@master30 ~ 14:05:30# kubectl get endpoints web
NAME   ENDPOINTS                                            AGE
web    10.224.83.171:80,10.224.83.172:80,10.224.83.173:80   2m22s

7.3 故障注入:删掉一个 Pod 的首页 → 自动摘除

root@master30 ~ 14:06:12# kubectl exec -it web-5d6964b9f5-6kwvt -- rm -f htdocs/index.html

# 90 次访问统计:只剩两个 Pod 的响应(6kwvt 已被摘除)
root@master30 ~ 14:06:47# for i in {1..90};do curl -s 10.108.251.21;done|sort |uniq -c
     45 web-5d6964b9f5-9wj4q
     45 web-5d6964b9f5-blz4z

root@master30 ~ 14:06:59# kubectl get pod
NAME                   READY   STATUS    RESTARTS   AGE
web-5d6964b9f5-6kwvt   0/1     Running   0          3m59s
web-5d6964b9f5-9wj4q   1/1     Running   0          3m59s
web-5d6964b9f5-blz4z   1/1     Running   0          3m59s

关键输出:删首页的 6kwvt 变成 0/1(NotReady),但 RESTARTS 仍为 0(readiness 不重启容器);90 次 curl 全部命中两个 Ready Pod(各 45 次)。kubectl get endpoints web 稍等片刻后同样只剩 2 个地址。清理:

root@master30 ~ 14:07:14# kubectl delete deployments.apps web
deployment.apps "web" deleted
root@master30 ~ 14:47:37# kubectl delete ns health
namespace "health" deleted

↑ 回到目录


八、认证(一):创建用户与证书签发

目标:在 worker31 上生成用户 ningcode 的私钥与 CSR,由 master30 用集群 CA 签发证书,再用证书+私钥配置独立的 kubeconfig 访问集群。命令提示符分别保留 root@worker31(客户端)与 root@master30(控制平面)时间戳。

8.1 客户端生成私钥与 CSR(worker31)

root@worker31 ~ 15:27:05# openssl genrsa -out ningcode.key 2048

root@worker31 ~ 15:27:37# openssl req -new -key ningcode.key -out ningcode.csr -subj '/CN=ningcode/O=kubernets'

CN=ningcode 即 Kubernetes 用户名,O=kubernets 是组织(日志原文如此,规范写法应为 O=kubernetes)。顺手想再生成一个 servera 证书,但私钥没生成导致报错:

root@worker31 ~ 15:28:17# 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=ningcode@lab.example.com"
Could not open file or uri for loading private key from servera.key
40179013AB790000:error:16000069:STORE routines:ossl_store_get0_loader_int:unregistered scheme:../crypto/store/store_register.c:237:scheme=file
40179013AB790000:error:80000002:system library:file_open:No such file or directory:../providers/implementations/storemgmt/file_store.c:267:calling stat(servera.key)
# 把 CSR 发给 master30
root@worker31 ~ 15:30:32# scp ningcode.csr root@master30:
ningcode.csr    100%  915     1.7MB/s   00:00

8.2 master 用集群 CA 签发证书(master30)

root@master30 ~ 15:26:51# openssl x509 -req -in ningcode.csr \
-CA /etc/kubernetes/pki/ca.crt \
-CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial \
-out ningcode.crt -days 365
Certificate request self-signature ok
subject=CN = ningcode, O = kubernets

# 签发完成后把证书回传给 worker31
root@master30 ~ 15:43:03# scp ningcode.crt root@worker31:~
ningcode.crt    100% 1017     1.9MB/s   00:00

第一次签发用了 -days 1095(3 年),后面重签为 -days 365(1 年)。签发对象就是集群 CA 文件 /etc/kubernetes/pki/ca.crt + ca.key,只有 master 上有。

↑ 回到目录


8.3 证书验证(worker31)

# 查看 CA 证书:Issuer/Subject 均为 CN = kubernetes,有效期 10 年
root@worker31 ~ 15:43:25# openssl x509 -in ca.crt -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 5842064140364811621 (0x51132e62117e0d65)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = kubernetes
        Validity
            Not Before: Aug  5 05:43:04 2026 GMT
            Not After : Aug  2 05:48:04 2036 GMT
        Subject: CN = kubernetes
        ......
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment, Certificate Sign
            X509v3 Basic Constraints: critical
                CA:TRUE
        ......

# 查看用户证书:Issuer 是 kubernetes CA,Subject 是 CN=ningcode, O=kubernets
root@worker31 ~ 15:44:25# openssl x509 -in ningcode.crt -text -noout
Certificate:
    Data:
        Version: 1 (0x0)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = kubernetes
        Validity
            Not Before: Aug 13 07:43:03 2026 GMT
            Not After : Aug 13 07:43:03 2027 GMT
        Subject: CN = ningcode, O = kubernets
        ......

# 查看私钥
root@worker31 ~ 15:44:32# openssl rsa -in ningcode.key -text -noout
Private-Key: (2048 bit, 2 primes)
modulus:
    00:b0:a6:45:f1:00:fb:17:6b:f1:36:5f:42:c1:da:
    ......

验证通过:ningcode.crtCN = kubernetes 的集群 CA 签发,有效期 1 年。这样客户端就拿到了“身份凭证”。

↑ 回到目录


九、认证(二):kubeconfig 配置

在 master30 上导出 admin 的 kubeconfig 作为模板,连同用户证书、CA 证书一起 scp 到 worker31,再在 worker31 上用 kubectl config set-* 改造成 ningcode 的独立配置。

9.1 导出模板并下发(scp 排错)

# 导出 kubeconfig 模版
root@master30 ~ 15:32:48# kubectl config view > config.tpl

# 将kubeconfig模板、ningcode.crt和kubernetes的ca证书发给客户端
# 先打错用户名 roor@(密码认证被拒),又写错主机 client(解析不了),最后才成功:
root@master30 ~ 15:33:18# scp config.tpl ningcode.crt /etc/kubernetes/pki/ca.crt roor@worker31:~
roor@worker31's password:
Permission denied, please try again.

root@master30 ~ 15:34:59# scp config.tpl ningcode.crt /etc/kubernetes/pki/ca.crt root@client:~
ssh: Could not resolve hostname client: Temporary failure in name resolution
scp: Connection closed

root@master30 ~ 15:36:45# scp config.tpl ningcode.crt /etc/kubernetes/pki/ca.crt root@worker31:~
config.tpl       100%  453     1.1MB/s   00:00
ningcode.crt     100% 1017     1.3MB/s   00:00
ca.crt           100% 1107     1.2MB/s   00:00

9.2 set-cluster / set-credentials / set-context(worker31)

# worker31 直接创建独立的 config(导出的 config.tpl 实际未使用;注意模板是从 admin 视角导出的,自带 admin 用户,后面小节会说明)
# 注意:set-cluster 要在客户端节点(worker31)执行——在 master30 上执行会因找不到 ca.crt 报错
root@worker31 ~ 15:31:47# kubectl config set-cluster kubernetes --kubeconfig=config --certificate-authority=ca.crt --embed-certs
Cluster "kubernetes" set.

root@worker31 ~ 15:39:34# kubectl config set-credentials ningcode --kubeconfig=config --client-key=ningcode.key --client-certificate=ningcode.crt --embed-certs
User "ningcode" set.

root@worker31 ~ 15:40:26# kubectl config set-context ningcode --kubeconfig=config --namespace=default --cluster=kubernetes --user=ningcode
Context "ningcode" created.

中间误把一段“注释 + 命令块”直接粘贴到了终端(rm -f config 后把 # 设置集群/用户/上下文/切换上下文 整段执行),相当于把上面的命令重新跑了一遍,最终结果一致:Switched to context "ningcode"。三个子命令的含义:

  • set-cluster:写集群地址与 CA(--embed-certs 把证书内容内嵌进文件)
  • set-credentials:写用户私钥与客户端证书
  • set-context:把“集群 + 用户 + 命名空间”组合成一个上下文

↑ 回到目录


9.3 坑:server 地址为空 → localhost:8080 refused

# 第一次用新 config 访问:连的是 localhost:8080,被拒
root@worker31 ~ 15:44:35# kubectl get nodes --kubeconfig=config
E0813 15:44:39.441749  214295 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
......
The connection to the server localhost:8080 was refused - did you specify the right host or port?

# 检查发现 server 地址是空的
root@worker31 ~ 15:44:39# cat config|grep server:
    server: ""

# 用 vim 补上集群 API Server 地址
root@worker31 ~ 15:45:13# vim config
# server: https://master30.ningcode.cn:6443

root@worker31 ~ 15:46:41# kubectl get nodes --kubeconfig=config
NAME                   STATUS   ROLES           AGE   VERSION
master30.ningcode.cn   Ready    control-plane   8d    v1.30.2
worker31.ningcode.cn   Ready    <none>          8d    v1.30.2
worker32.ningcode.cn   Ready    <none>          8d    v1.30.2
root@worker31 ~ 15:46:44# #原因是config文件的server地址为空,没有指定master

原因:set-cluster 时没有携带 --server=https://master30.ningcode.cn:6443,导致 config 里 server 为空,kubectl 只能退回到默认的 localhost:8080。补上 server 地址后一切正常。

9.4 以 ningcode 身份访问集群

root@worker31 ~ 15:49:33# kubectl config get-contexts --kubeconfig=config
CURRENT   NAME       CLUSTER      AUTHINFO   NAMESPACE
*         ningcode   kubernetes   ningcode   default

# 注意:worker31 上没有默认 kubeconfig,不带 --kubeconfig 的 kubectl 全部报 localhost:8080 refused
root@worker31 ~ 16:49:08# kubectl get nodes
E0813 16:51:27.316100  248858 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
......

# 带上 --kubeconfig=config 用 ningcode 身份建 Pod(此时 ningcode 已被授予 cluster-admin,见第十章)
root@worker31 ~ 16:52:10# kubectl run web --image=hub.laoma.cloud/library/httpd --image-pull-policy=IfNotPresent -n default --kubeconfig=config
pod/web created

root@worker31 ~ 16:53:08# kubectl get pod -n default --kubeconfig=config
NAME   READY   STATUS    RESTARTS   AGE
web    1/1     Running   0          17s

root@worker31 ~ 16:58:10# kubectl get pods -o wide --kubeconfig=config
NAME   READY   STATUS    RESTARTS   AGE   IP               NODE                   NOMINATED NODE   READINESS GATES
web    1/1     Running   0          6m    10.224.195.158   worker31.ningcode.cn   <none>           <none>

# 尝试用 ningcode.kubeconfig(master 上没生成该文件,报错)
root@worker31 ~ 17:00:01# kubectl get pod -n default --kubeconfig=ningcode.kubeconfig
error: stat ningcode.kubeconfig: no such file or directory

root@worker31 ~ 17:03:30# #这个 config 是 admin 的 kubeconfig,是集群超级管理员,跟 ningcode 用户没关系

最后的注释很关键:worker31 上这份 config 是从 admin 模板改出来的,里面还保留着 admin 用户;当前使用的上下文是 ningcode,真正生效的身份是 ningcode(此时有 cluster-admin 权限)。要做细粒度 RBAC 验证,需要先撤掉过大的授权。

↑ 回到目录


十、授权:RBAC 角色与绑定

10.1 ClusterRoleBinding:快速授予集群管理员

root@master30 ~ 15:39:31# # 授权用户ningcode集群管理员角色,后续详细讲解角色管理
root@master30 ~ 15:41:07# kubectl create clusterrolebinding ningcode-admin --clusterrole=cluster-admin --user=ningcode
clusterrolebinding.rbac.authorization.k8s.io/ningcode-admin created

cluster-admin 是集群内置的超级管理员 ClusterRole;绑定后 ningcode 对全集群有全部权限(第 9 章 worker31 能 kubectl run web 就是靠它)。下面开始精细化的 Role / RoleBinding。

↑ 回到目录


10.2 创建 Role:get / list / watch Pod

# kubectl create role -h 帮助较长,省略 ......
# 先生成 YAML 看效果(dry-run)
root@master30 ~ 16:43:39# kubectl create role pod-role --verb=get,list,watch --resource=pods -n default --dry-run=client -o 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

# 正式创建并查看
root@master30 ~ 16:43:51# kubectl create role pod-role --verb=get,list,watch --resource=pods -n default
role.rbac.authorization.k8s.io/pod-role created

root@master30 ~ 16:44:07# 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]

10.3 Role 限定资源名(resourceNames)

# 只允许 get 指定名字的 Pod(readablepod / anotherpod)
root@master30 ~ 16:44:13# kubectl create role pod-role --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
role.rbac.authorization.k8s.io/pod-role created

root@master30 ~ 16:45:47# kubectl get roles pod-role -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  creationTimestamp: "2026-08-13T08:44:31Z"
  name: pod-role
  namespace: auth
  ......
rules:
- apiGroups:
  - ""
  resourceNames:
  - readablepod
  - anotherpod
  resources:
  - pods
  verbs:
  - get

注意坑:第二次创建没有带 -n default,而当前 context 的命名空间是 auth,所以这个带 resourceNames 的 pod-role 建到了 auth 命名空间(yaml 里 namespace: auth)。默认命名空间里那个 pod-role 仍是 get/list/watch。

10.4 编辑 Role 增加 create 权限

root@master30 ~ 16:44:50# kubectl edit roles -n default pod-role
role.rbac.authorization.k8s.io/pod-role edited

root@master30 ~ 16:46:09# # 在verbs下添加相应权限  - create

kubectl editdefault 命名空间里 pod-role 的 verbs 增加 create

↑ 回到目录


10.5 RoleBinding:把用户绑到 Role

# 先生成 YAML(dry-run),再正式创建
root@master30 ~ 16:46:25# kubectl create rolebinding default-pod-ningcode -n default --role=pod-role --user=ningcode --dry-run=client -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  creationTimestamp: null
  name: default-pod-ningcode
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: pod-role
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: ningcode

root@master30 ~ 16:47:54# kubectl create rolebinding default-pod-ningcode -n default --role=pod-role --user=ningcode
rolebinding.rbac.authorization.k8s.io/default-pod-ningcode created

root@master30 ~ 16:48:37# kubectl get rolebindings -n default default-pod-ningcode
NAME                   ROLE            AGE
default-pod-ningcode   Role/pod-role   10s

root@master30 ~ 16:48:47# kubectl describe rolebindings -n default default-pod-ningcode
Name:         default-pod-ningcode
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  Role
  Name:  pod-role
Subjects:
  Kind  Name      Namespace
  ----  ----      ---------
  User  ningcode

至此:用户 ningcode(default 命名空间)→ RoleBinding default-pod-ningcode → Role pod-role(pods get/list/watch/create)。第 9 章 worker31 上 --kubeconfig=config 的所有操作能成功,是因为当时 cluster-admin 绑定还在;做细粒度权限验证前应先删除 ningcode-admin 这个 clusterrolebinding,否则会被超级管理员权限覆盖。

10.6 清理 RBAC 资源

# 误以为绑定建在 kube-system,删除时 NotFound(实际绑定在 default 命名空间)
root@master30 ~ 16:52:31# kubectl delete rolebindings default-pod-laoma -n kube-system
Error from server (NotFound): rolebindings.rbac.authorization.k8s.io "default-pod-laoma" not found
root@master30 ~ 16:54:32# kubectl delete rolebindings.rbac.authorization.k8s.io default-pod-ningcode -n kube-system
Error from server (NotFound): rolebindings.rbac.authorization.k8s.io "default-pod-ningcode" not found

root@master30 ~ 16:55:04# kubectl get rolebindings.rbac.authorization.k8s.io
No resources found in auth namespace.
root@master30 ~ 16:56:27# kubectl get rolebindings -n default default-pod-ningcode
NAME                   ROLE            AGE
default-pod-ningcode   Role/pod-role   8m31s

# 在正确的命名空间删除
root@master30 ~ 16:57:08# kubectl delete rolebindings default-pod-ningcode -n default
rolebinding.rbac.authorization.k8s.io "default-pod-ningcode" deleted
root@master30 ~ 16:57:56# kubectl delete roles pod-role -n default
role.rbac.authorization.k8s.io "pod-role" deleted

↑ 回到目录


十一、环境清理与恢复

资源 命令 说明
HPA 命名空间 kubectl delete ns web 10:41 清理,回收 deployment/hpa/svc
配额命名空间 kubectl delete ns quota 13:34 清理,回收 quota/limitrange/stress
探针命名空间 kubectl delete ns health 14:47 清理
RBAC 绑定 kubectl delete rolebindings default-pod-ningcode -n default 16:57 清理
RBAC 角色 kubectl delete roles pod-role -n default 16:57 清理
认证命名空间 auth 本次未删除(上下文切到 auth 后未回收) kubectl delete ns auth
测试大文件 rm -rf /www/big.img 工作节点本地文件,实验后删除

提醒:auth 命名空间与 ningcode-admin clusterrolebinding、worker31 上的 ningcode.key/.csr/.crt/config 在实验结束后仍保留,复现或继续实验可直接复用;若要彻底清理,需在 master30 删除命名空间与绑定,在 worker31 删除相关文件。

↑ 回到目录


十二、常见错误与排查(重点)

以下全部来自本次实验的真实报错,按主题分组。

12.1 HPA / 压测专项

现象 原因 修复
kubectl get svc -aunknown shorthand flag: 'a' -a 不是合法参数,应为 -A kubectl get svc -A
apply deployment 报 namespaces "web" not found context 指向 web 命名空间,但 ns 还没创建 kubectl create namespace web 再 apply
HPA 显示 memory: <unknown>/60% 指标采集有延迟(metrics-server 拉取 + HPA 轮询) 等 15–30 秒再 kubectl get hpa
压测半天副本不增加(负载上不去) 节点内存有限,实际使用率达不到 60% 把 target 从 60% 调到 20%(本次改后 2→3)
ab 报 Connection refused (111) Service 重建后 ClusterIP 变了,还在压旧 IP kubectl get svc 拿新 IP 重新压
ab 压测输出刷屏 循环压测日志太长 后台运行 & 或重定向到文件,只看监控窗口

12.2 配额 / 限额专项

现象 原因 修复
Deployment 3 副本只起 2 个 ResourceQuota 限制 pods=2 kubectl describe rs 看 FailedCreate 事件;kubectl patch resourcequotas myquota -p '{"spec":{"hard":{"pods":10}}}' 调大配额
创建 Pod 报 must specify limits.cpu/limits.memory/requests.cpu/requests.memory 配额声明了计算资源,要求 Pod 必须显式声明 在 Pod 的 resources 里补上 requests/limits
Pod 报 exceeded quota: requested: requests.cpu=2... limited: requests.cpu=1 单个 Pod 的请求超过配额总上限 减小 Pod 请求,或调大配额
stress 容器 OOMKilled + CrashLoopBackOff 压测内存超过 limit(被内核 OOM 杀掉) 减小压测参数;这是预期演示效果
kubectl top podsmetrics not available yet metrics-server 尚未采集到数据 等几秒再查

12.3 探针专项

现象 原因 修复
kubectl exec web -rm / --rm 报 unknown flag exec 的参数分隔符用错 kubectl exec web -- rm -f ...-- 后才是容器内命令)
liveness 失败后 RESTARTS 变为 1 探针连续失败,kubelet 重启容器 检查探针 path/port/command 是否正确,容器内是否真的可探测
busybox 一直 CrashLoopBackOff busybox 没有常驻进程,启动即退出 args 里加 sleep 保活(本次 touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy; sleep 100
探针事件 Liveness probe failed: ls: /tmp/healthy: No such file or directory exec 探测命令返回非 0 预期故障演示,容器会被自动重启
readiness 失败后 Pod 0/1 但 RESTARTS 不变 readiness 只影响就绪状态,不重启容器 正常现象;流量已从 endpoints 摘除
readiness 摘除有延迟 periodSeconds 5s × failureThreshold 3 稍等片刻再验证 endpoints

↑ 回到目录


12.4 认证 / kubeconfig / RBAC 专项

现象 原因 修复
openssl req 报 Could not open file or uri for loading private key from servera.key 私钥文件不存在(没生成或名字打错) openssl genrsa -out servera.key 2048,再生成 CSR
scp 用 roor@worker31 反复要密码 用户名打错 改为 root@worker31
scp 用 root@clientCould not resolve hostname client 主机名写错(client 不存在) 写真实主机名 worker31
master30 上 set-clustercould not stat certificate-authority file ca.crt 在 master 上执行了本应在客户端执行的命令,master 当前目录没有 ca.crt 在 worker31(客户端)上执行 kubectl config set-cluster ... --certificate-authority=ca.crt
kubectl get nodes --kubeconfig=configlocalhost:8080 ... connection refused config 里 server: 为空(set-cluster 没带 --server vim configserver: https://master30.ningcode.cn:6443
worker31 上不带 --kubeconfig 的 kubectl 全部 refused worker31 没有默认的 ~/.kube/config 每次命令加 --kubeconfig=config,或 export KUBECONFIG=...
--kubeconfig=ningcode.kubeconfigno such file or directory master 上根本没生成 ningcode.kubeconfig 文件(scp 时也报 stat 失败) 使用 worker31 自己组装的 config;或先在 master 上生成该文件再下发
删除 rolebinding 报 NotFound 命名空间写错(绑定在 default,却去 kube-system 删) kubectl get rolebindings -A 确认位置后再删
create role 第二次不带 -n,角色建到了 auth 命名空间 当前 context 的 namespace 是 auth 创建资源显式加 -n default,或用 kubectl config set-context --current --namespace 切回
ningcode 权限“看起来”比 Role 大 cluster-admin 的 ClusterRoleBinding 还没删,覆盖了细粒度 Role 验证细粒度权限前先 kubectl delete clusterrolebinding ningcode-admin
kubectl get roles pod-role -o yaml 看到 namespace: auth 查看的是 auth 里的同名 Role -n default 指定命名空间查看

12.5 操作安全提醒

现象 说明
输入 rm -rf / 后按 Tab 列出了根目录所有文件 Tab 补全会把 / 展开成根目录下所有路径,一旦回车后果不堪设想。本次是补全后接着输入 /www/big.img,实际执行的是 rm -rf /www/big.img任何时候都不要对 rm -rf / 回车

↑ 回到目录


十三、命令速查表与小结

13.1 命令速查表

场景 命令
创建内存 HPA kubectl apply -f hpa-mem.yamlkubectl get hpa 查看)
监控 HPA 与用量 watch -n 4 'kubectl get hpa;echo;kubectl top pods'
制造下载压力 ab -n 300000 -c 100 http://<svc-ip>/big.img 或循环 while true;do ...;sleep 1;done
创建对象配额 kubectl create quota myquota --hard=pods=2,services=3,secrets=5,persistentvolumeclaims=10
查看/调整配额 kubectl get resourcequotas / kubectl describe quota myquota / kubectl patch resourcequotas myquota -p '{"spec":{"hard":{"pods":10}}}'
创建 LimitRange kubectl apply -f limits.yamlkubectl get limitranges / kubectl describe limitranges mylimit
探针三方式 httpGet: path/portexec: commandtcpSocket: port,配合 failureThreshold/periodSeconds 等
查看重启/事件 kubectl get pods(RESTARTS)、kubectl describe pod <pod>(Events)
生成密钥与 CSR openssl genrsa -out ningcode.key 2048openssl req -new -key ningcode.key -out ningcode.csr -subj '/CN=ningcode/O=kubernetes'
CA 签发证书 openssl x509 -req -in ningcode.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out ningcode.crt -days 365
查看证书 openssl x509 -in ningcode.crt -text -noout
配置 kubeconfig kubectl config set-cluster/set-credentials/set-context --kubeconfig=config --embed-certs
指定配置访问 kubectl get nodes --kubeconfig=config
授权 cluster-admin kubectl create clusterrolebinding ningcode-admin --clusterrole=cluster-admin --user=ningcode
创建 Role kubectl create role pod-role --verb=get,list,watch --resource=pods -n default
Role 限定资源名 kubectl create role pod-role --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
绑定用户 kubectl create rolebinding default-pod-ningcode -n default --role=pod-role --user=ningcode
查看 RBAC kubectl get roles / rolebindings / clusterrolebindingskubectl describe roles <名> -n <ns>
生成 YAML 预览 上述 create 命令加 --dry-run=client -o yaml

↑ 回到目录


13.2 小结

  1. HPA 内存扩容要看 Utilization 基准averageUtilization 是相对 Pod 内存限额的使用率,指标采集有延迟;实验环境负载压不上去时把 target 调低(60%→20%)即可观察 2→3 副本的扩容过程
  2. ResourceQuota 管“总量”,LimitRange 管“默认值”:Quota 超限会直接拒绝创建(FailedCreate/exceeded quota),Quota 声明了 requests/limits 后每个 Pod 必须显式声明计算资源;LimitRange 会给没写 resources 的 Pod 自动注入 default/defaultRequest
  3. liveness 与 readiness 分工不同:liveness 失败 → kubelet 重启容器(RESTARTS+1);readiness 失败 → Pod 变 0/1 并从 Service Endpoints 摘除,流量只到 Ready Pod;两者都支持 httpGet / exec / tcpSocket 三种探测
  4. 认证三件套:客户端 genrsa + req 生成密钥与 CSR → master 用集群 CA openssl x509 -req 签发 → kubeconfig 里 set-cluster / set-credentials / set-context 组装身份;--embed-certs 把证书内嵌进文件
  5. kubeconfig 常见坑set-cluster 忘记 --server 会退化成 localhost:8080;--kubeconfig 文件名必须真实存在;worker 节点默认没有 kubeconfig,命令都要带 --kubeconfig
  6. RBAC 授权链路:Role(命名空间级)或 ClusterRole(集群级)→ RoleBinding/ClusterRoleBinding 绑定 User/ServiceAccount;权限验证前先删掉过大的 cluster-admin 绑定,否则细粒度 Role 形同虚设

本手册基于 master30_2026-08-13_10_13_37.log + worker31_2026-08-13_15_27_14.log 整理,命令时间戳均为原始记录。

更多推荐