Kubernetes 存储与pod管理全解(emptyDir · hostPath · NFS · PV/PVC · ConfigMap · Secret · ReplicaSet)

本手册由 master30_2026-08-07_9_08_22.log(8/7 全天实验)整理而成,在已建好的 kubeadm v1.30.2 集群(master30 + worker31/worker32)上完成。
三大主题:① 存储(emptyDir / hostPath / NFS / PV/PVC)→ ② 配置管理(ConfigMap / Secret)→ ③ 控制器(ReplicaSet)。命令提示符保留真实时间戳,报错与修复全部来自实际操作。

📑 目录

#章节核心内容
整体框架与实验总览实验流程图、三大主题、节点环境
实验准备:命名空间与工作目录创建 storage 命名空间、kubens 切换、目录组织
emptyDir:Pod 内临时共享存储多容器共享 /data、宿主机落盘位置、生命周期
hostPath:宿主机目录挂载挂载 /busyboxdir、readOnly 控制、crictl 验证
NFS:网络共享存储搭建 NFS 服务端、Pod 挂载、双向读写验证
PV/PVC:持久化存储PV 定义、PVC 申请、绑定关系、访问验证
ConfigMap:配置注入环境变量注入、文件挂载、haproxy 负载均衡实战
Secret:敏感信息管理literal/file/env-file/docker-registry 四种创建方式、wordpress 实战
控制器:ReplicaSet副本保持、自愈、级联删除、orphan 孤儿
常见错误与排查(重点)本次实验全部报错:原因 + 修复
十一命令速查表存储 / ConfigMap / Secret / 控制器常用命令

一、整体框架与实验总览

1.1 实验流程图

集群已就绪
master30 + worker31/32

创建 storage 命名空间
kubens 切换 + 目录组织

存储实验
emptyDir → hostPath → NFS → PV/PVC

ConfigMap 实验
环境变量 → 文件挂载 → haproxy 负载均衡

Secret 实验
四种创建方式 → wordpress 实战

控制器实验
ReplicaSet 自愈/级联/孤儿

错误复盘 + 命令速查

1.2 节点环境

节点主机名IP角色
master30master30.ningcode.cn10.1.8.30控制平面(同时充当 NFS 服务端)
worker31worker31.ningcode.cn10.1.8.31工作节点
worker32worker32.ningcode.cn10.1.8.32工作节点
  • Pod 网段 10.224.0.0/16(Pod IP 形如 10.224.83.x / 10.224.195.x)
  • 私有镜像仓库 hub.laoma.cloud
  • 切换命名空间后命令提示符会显示当前目录:root@master30 storage 10:07:56#

1.3 主题速览

主题核心知识点日志时间
存储emptyDir 共享、hostPath 直连宿主机、NFS 跨节点、PV/PVC 解耦09:37–11:05
ConfigMapenv 注入、volume 挂载、haproxy 动态配置13:52–14:59
Secretbase64 存储、四种创建方式、私有仓库拉镜像14:59–16:00
控制器ReplicaSet 副本保持、自愈、删除策略16:18–16:30

↑ 回到目录


二、实验准备:命名空间与工作目录

2.1 创建 storage 命名空间并切换

# 注意:kubens 只是切换工具,没有 create 子命令,创建命名空间要用 kubectl
root@master30 ~ 09:37:46# kubectl create ns storage
namespace/storage created

# 用 kubens 切换当前命名空间
root@master30 ~ 09:40:25# kubens storage
Context "kubernetes-admin@kubernetes" modified.
Active namespace is "storage".

# 等价写法(直接改 kubeconfig 当前 context)
root@master30 ~ 09:41:27# kubectl config set-context --current --namespace=storage

2.2 建立工作目录

root@master30 ~ 09:41:27# mkdir storage
root@master30 ~ 09:44:00# cd storage/
root@master30 storage 09:44:02# vim pod1.yaml

之后的实验都按主题建目录:storage/(存储)、configmap/(配置)、controllers/(控制器),YAML 文件与命名空间一一对应,方便管理与清理。

↑ 回到目录


三、emptyDir:Pod 内临时共享存储

3.1 概念

  • emptyDir 是 Pod 级临时目录:生命周期与 Pod 相同,Pod 删除数据即消失
  • 同一个 Pod 里的多个容器可以共享该目录(本实验两个 busybox 容器共享 /data
  • 应用场景:容器间交换文件、临时缓存

3.2 YAML 示例(pod-with-emptyDir.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: busybox
  labels:
    app: busybox
spec:
  containers:
  - name: busybox1
    image: busybox
    command: ["sh", "-c", "echo Hello Kubernetes! && sleep 3600"]
    volumeMounts:
    - name: datavolume
      mountPath: /data
  - name: busybox2
    image: busybox
    command: ["sh", "-c", "echo Hello Kubernetes! && sleep 3600"]
    volumeMounts:
    - name: datavolume
      mountPath: /data
  volumes:
  - name: datavolume
    emptyDir: {}

3.3 验证:多容器共享同一目录

root@master30 storage 10:07:04# kubectl apply -f pod-with-emptyDir.yaml
pod/busybox created
root@master30 storage 10:07:56# kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
busybox   2/2     Running   0          7s

# 容器1 写入文件(注意 -- 与命令之间有空格)
root@master30 storage 10:10:50# kubectl exec busybox -c busybox1 -- touch /data/b1-f1

# 容器2 能看到容器1 写的文件 → 证明共享
root@master30 storage 10:10:56# kubectl exec busybox -c busybox2 -- ls /data
b1-f1

3.4 宿主机真实落盘位置

emptyDir 实际存放在调度到的节点上,路径规则:/var/lib/kubelet/pods/<PodUID>/volumes/kubernetes.io~empty-dir/<卷名>/

# 在 worker31 上查看(本实验 Pod 调度到 worker31)
root@worker31 ~ 10:11:57# ls /var/lib/kubelet/pods/21db6f4d-61b1-4d3c-a900-de5f9b66a040/volumes/kubernetes.io~empty-dir/datavolume/
b1-f1

describe 中可看到卷类型标注:Type: EmptyDir (a temporary directory that shares a pod's lifetime)

要点: emptyDir 的数据随 Pod 删除而消失(不持久),跨节点迁移或 Pod 重建后数据不在。

↑ 回到目录


四、hostPath:宿主机目录挂载

4.1 概念

  • hostPath 直接把宿主机某个目录挂载进容器,容器内写的文件宿主机立即可见
  • 风险:Pod 调度到哪台节点,用的就是哪台节点的目录;Pod 删除数据仍保留在宿主机

4.2 YAML 示例(pod-with-hostPath.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  containers:
  - name: busybox1
    image: busybox
    command: ["sh", "-c", "echo Hello Kubernetes! && sleep 3600"]
    volumeMounts:
    - name: datavolume
      mountPath: /data
  - name: busybox2
    image: busybox
    command: ["sh", "-c", "echo Hello Kubernetes! && sleep 3600"]
    volumeMounts:
    - name: datavolume
      mountPath: /data
  - name: busybox3
    image: busybox
    command: ["sh", "-c", "echo Hello Kubernetes! && sleep 3600"]
    volumeMounts:
    - name: datavolume
      mountPath: /data
      readOnly: true        # 只读挂载:默认 false(读写)
  volumes:
  - name: datavolume
    hostPath:
      path: /busyboxdir     # 宿主机目录

4.3 验证读写与只读

root@master30 storage 10:13:36# kubectl apply -f pod-with-hostPath.yaml
pod/busybox created

# 先查调度到哪个节点,再去该节点验证
root@master30 storage 10:14:07# kubectl describe pod busybox | grep Node
Node:  worker31.ningcode.cn/10.1.8.31

# 读写容器写文件
root@master30 storage 10:16:27# kubectl exec busybox -c busybox2 -- touch /data/b2-f2

# 宿主机 /busyboxdir 立即可见(worker31 上执行)
root@worker31 ~ 10:17:19# ls /busyboxdir/
b1-f1  b2-f2

# 只读容器(busybox3)写入会失败 → readOnly 生效
root@master30 storage 10:18:23# kubectl exec busybox -c busybox3 -- touch /data/b3-f3
# (touch: cannot touch '/data/b3-f3': Read-only file system)

容器内路径与宿主路径不同名没关系,关键看 hostPath.path 指向哪;用 crictl 也能验证:
crictl inspect <容器ID> | grep busyboxdir → 输出 "hostPath": "/busyboxdir"

要点: hostPath 不跨节点共享、不随 Pod 管理生命周期,生产慎用(适合读取宿主配置、日志等场景)。

↑ 回到目录


五、NFS:网络共享存储

5.1 概念

  • NFS(Network File System)把一台服务器的目录通过网络共享出去,所有节点都能访问同一份数据,天然跨节点共享
  • 本实验:master30 做 NFS 服务端,导出 /nfsshares,Pod 挂载后读写互见

5.2 服务端搭建(master30)

# ① 安装 NFS 服务端
root@master30 storage 10:34:10# apt install -y nfs-kernel-server

# ② 创建共享目录(777 权限便于容器写入)
root@master30 storage 10:34:30# mkdir -m 777 /nfsshares
root@master30 storage 10:34:39# echo hello > /nfsshares/index.html

# ③ 配置 /etc/exports:共享 /nfsshares,允许所有主机读写
root@master30 storage 10:34:55# cat << EOF > /etc/exports
/nfsshares *(rw)
EOF

# ④ 重启服务生效
root@master30 storage 10:35:07# systemctl restart nfs-server.service

5.3 Pod 挂载 NFS(pod-with-nfs.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: hub.laoma.cloud/library/nginx
    volumeMounts:
    - name: nfs
      mountPath: /usr/share/nginx/html
  volumes:
  - name: nfs
    nfs:
      server: 10.1.8.30        # NFS 服务端
      path: /nfsshares          # 共享路径

5.4 验证:容器 ↔ 宿主机双向可见

root@master30 storage 10:37:25# kubectl apply -f pod-with-nfs.yaml
pod/nginx created

# ① 调度节点上能看到 nfs 挂载(worker31 上执行 df)
root@worker31 ~ 10:38:40# df | grep nfsshare
10.1.8.30:/nfsshares  101590016 ... /var/lib/kubelet/pods/<uid>/volumes/kubernetes.io~nfs/nfs

# ② 容器里能看到服务端预置的 index.html
root@master30 storage 10:39:21# kubectl exec nginx -- ls /usr/share/nginx/html
index.html

# ③ 容器里写文件,服务端立即可见
root@master30 storage 10:39:47# kubectl exec nginx -- touch /usr/share/nginx/html/test.html
root@master30 storage 10:40:06# ls /nfsshares/
index.html  test.html

# ④ Pod 删除后,NFS 上的数据仍然保留
root@master30 storage 10:40:13# kubectl delete pod nginx --force
root@master30 storage 10:40:28# ls /nfsshares/
index.html  test.html

要点: NFS 解决了跨节点共享与数据持久两个问题,是自建集群最常用的共享存储方案(生产建议用云厂商的 NAS/EFS 或 NFS 高可用方案)。

↑ 回到目录


六、PV/PVC:持久化存储

6.1 概念:存储与使用解耦

  • PV(PersistentVolume):集群管理员预先准备好的一块存储(本实验为 NFS 5Gi)
  • PVC(PersistentVolumeClaim):用户申请使用存储(要多大、什么访问模式)
  • 绑定关系:PVC 申请 → 自动匹配符合条件的 PV → Bound;Pod 再通过 PVC 使用存储
  • 好处:应用只写 PVC,不用关心底层是 NFS 还是云盘

6.2 创建 PV(pv.yaml)

apiVersion: v1
kind: PersistentVolume
metadata:
  name: web
spec:
  capacity:
    storage: 5Gi
  accessModes:
  - ReadWriteOnce          # RWO:单节点读写
  persistentVolumeReclaimPolicy: Retain   # 释放后保留数据
  nfs:
    server: 10.1.8.30
    path: /nfsshares
root@master30 storage 10:58:28# kubectl apply -f pv.yaml
persistentvolume/web created
root@master30 storage 10:58:35# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   AGE
web    5Gi        RWO            Retain           Available                         6s

6.3 创建 PVC(pvc.yaml)

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: webclaim
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
root@master30 storage 11:04:08# kubectl apply -f pvc.yaml
persistentvolumeclaim/webclaim created
# 此时 PV 状态由 Available 变为 Bound(被 webclaim 绑定)

⚠️ 顺序不能反:先建 PV,再建 PVC,最后建 Pod。本次实验先 apply Pod 再建 PVC,Pod 一直 Pending(见第十章错误 3)。

6.4 Pod 使用 PVC(pod-with-pvc.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    name: web
spec:
  containers:
  - name: web
    image: hub.laoma.cloud/library/nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - name: web-persistent-storage
      mountPath: /usr/share/nginx/html
  volumes:
  - name: web-persistent-storage
    persistentVolumeClaim:
      claimName: webclaim

6.5 验证:Pod 读到 NFS 里的页面

root@master30 storage 11:04:10# kubectl apply -f pod-with-pvc.yaml
pod/web created
root@master30 storage 11:04:39# kubectl get pods -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE
web    1/1     Running   0          15s   10.224.83.129   worker32.ningcode.cn

# 访问 Pod,返回 NFS 上的 index.html(内容是 hello)
root@master30 storage 11:04:41# curl 10.224.83.129
hello

# 在 NFS 服务端新增文件,Pod 内立即能访问 → 数据在 Pod 之外,Pod 删了数据还在
root@master30 storage 11:04:50# echo test > /nfsshares/test.html
root@master30 storage 11:05:09# curl http://10.224.83.129/test.html
test

要点: PVC 是"存储申请",Pod 只认 PVC 名字;换底层存储(NFS → 云盘)时应用 YAML 不用改。

↑ 回到目录


七、ConfigMap:配置注入

7.1 概念

  • ConfigMap 用于把配置与镜像解耦:改配置不用重新构建镜像
  • 两种使用方式:① 注入环境变量(env)② 以卷挂载成文件(volume)
  • 创建方式:--from-literal(命令行键值)、--from-file(文件/目录)

7.2 方式一:环境变量注入(mysql 密码示例)

# ① 先创建 ConfigMap
root@master30 configmap 13:54:42# kubectl create configmap mysql --from-literal=password=redhat
configmap/mysql created
root@master30 configmap 13:55:22# kubectl get configmaps mysql -o yaml
apiVersion: v1
data:
  password: redhat
kind: ConfigMap
...
# pod-with-env-from-configmap.yaml:env 里用 configMapKeyRef 引用
apiVersion: v1
kind: Pod
metadata:
  name: mysql
  labels:
    name: mysql
spec:
  containers:
  - image: hub.laoma.cloud/library/mysql:latest
    name: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
        configMapKeyRef:
          name: mysql      # 引用 ConfigMap 名
          key: password    # 引用其中的键
root@master30 configmap 13:57:29# kubectl apply -f pod-with-env-from-configmap.yaml
pod/mysql created
root@master30 configmap 13:57:39# kubectl get pods mysql -o wide -w
mysql   1/1     Running   0    16s   10.224.83.131   worker32.ningcode.cn

# ② 进容器验证环境变量生效
root@master30 configmap 13:57:57# kubectl exec -it mysql -- bash
bash-5.1# echo $MYSQL_ROOT_PASSWORD
redhat
bash-5.1# exit

# ③ 宿主机装 mysql-client 直接连容器(密码来自 ConfigMap)
root@master30 configmap 13:58:38# apt install -y mysql-client
root@master30 configmap 13:58:52# mysql -uroot -predhat -h 10.224.83.131

注意:如果 Pod 引用了不存在的 ConfigMap,Pod 会卡在 CreateContainerConfigError,先创建 ConfigMap 再建 Pod(见第十章错误 2)。

7.3 方式二:挂载成文件(nginx 页面示例)

# ① 用 --from-file 把文件内容存进 ConfigMap
root@master30 configmap 13:55:41# echo hello world > index.html
root@master30 configmap 13:56:00# kubectl create configmap web1 --from-file=./index.html
configmap/web1 created

# 也支持整个目录(目录下每个文件变成一个键)
root@master30 configmap 13:56:17# echo error > error.html
root@master30 configmap 13:56:33# mkdir web2
root@master30 configmap 13:56:36# mv index.html error.html web2
root@master30 configmap 13:56:43# kubectl create configmap web2 --from-file=./web2
configmap/web2 created
root@master30 configmap 13:56:58# kubectl get configmaps web2 -o yaml
data:
  error.html: |
    error
  index.html: |
    hello world
# pod-with-volume-from-configmap.yaml:把整个 ConfigMap 挂载为 nginx 的 html 目录
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
  - name: nginx
    image: hub.laoma.cloud/library/nginx
    volumeMounts:
    - name: config
      mountPath: /usr/share/nginx/html
      readOnly: true
  volumes:
  - name: config
    configMap:
      name: web2          # 两个键 → html 目录下两个文件
root@master30 configmap 14:00:01# kubectl apply -f pod-with-volume-from-configmap.yaml
pod/web created
root@master30 configmap 14:00:24# kubectl exec web -- ls /usr/share/nginx/html
error.html
index.html
root@master30 configmap 14:00:49# curl http://10.224.195.132
hello world
root@master30 configmap 14:01:01# curl http://10.224.195.132/error.html
error

补充:只挂载 ConfigMap 里单个键时,用 items 指定(pod-cm-volume-single.yaml 实验:挂载 index.html 单文件,error.html 404——因为没挂载)。

7.4 实战:ConfigMap + haproxy 负载均衡

目标:两个 nginx Pod(webapp-1 / webapp-2)各显示自己的页面,haproxy 做轮询负载均衡,haproxy 配置本身也放在 ConfigMap 里

# ① 两个页面 ConfigMap
root@master30 configmap 14:51:39# kubectl create cm webapp-1 --from-literal=index.html="hello webapp-1" --from-literal=error.html="sorry, error."
configmap/webapp-1 created
root@master30 configmap 14:52:27# kubectl create cm webapp-2 --from-literal=index.html="hello webapp-2" --from-literal=error.html="sorry, error."
configmap/webapp-2 created

# ② 两个 nginx Pod,把各自的 ConfigMap 挂到 html 目录
root@master30 configmap 14:52:14# kubectl apply -f pod-webapp-1.yaml
pod/webapp-1 created
root@master30 configmap 14:52:45# kubectl apply -f pod-webapp-2.yaml
pod/webapp-2 created

# ③ 确认两个 Pod 的真实 IP(填 haproxy 配置要用)
root@master30 configmap 14:52:49# kubectl get pods -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP               NODE
webapp-1   1/1     Running   0          38s   10.224.83.138    worker32.ningcode.cn
webapp-2   1/1     Running   0          10s   10.224.195.136   worker31.ningcode.cn

# ④ 分别访问验证
root@master30 configmap 14:52:59# curl http://10.224.83.138
hello webapp-1
root@master30 configmap 14:53:10# curl http://10.224.195.136
hello webapp-2
# ⑤ haproxy 配置(haproxy.cfg):backend 写两个 Pod 的真实 IP
global
    daemon
    maxconn 256
defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
frontend http-in
    bind *:8080
    default_backend servers
backend servers
    server  app1 10.224.83.138:80 check
    server  app2 10.224.195.136:80 check
# ⑥ 把 haproxy 配置做成 ConfigMap,haproxy Pod 挂载 /usr/local/etc/haproxy
root@master30 configmap 14:54:18# kubectl create cm haproxy.cfg --from-file=haproxy.cfg=./haproxy.cfg
configmap/haproxy.cfg created
root@master30 configmap 14:54:43# kubectl apply -f haproxy.yaml
pod/haproxy created

# ⑦ 通过 haproxy 访问,轮询到两个页面 → 负载均衡生效
root@master30 configmap 14:55:36# curl http://10.224.83.139:8080
hello webapp-1
root@master30 configmap 14:55:54# curl http://10.224.83.139:8080
hello webapp-2

⚠️ 踩坑:haproxy.cfg 里 backend 一开始填了错误的 IP(10.224.84.80 等),curl 报 Connection refused / 503 Service Unavailable必须用 kubectl get pods -o wide 查到的真实 Pod IP,改完 haproxy.cfg 后重新 kubectl create cm haproxy.cfg --from-file=... 并重建 haproxy Pod(ConfigMap 挂载后不会热更新)。

↑ 回到目录


八、Secret:敏感信息管理

8.1 概念

  • Secret 与 ConfigMap 用法几乎一样,但值经过 base64 编码,适合存放密码、密钥、token
  • 注意:base64 只是编码不是加密,echo xxx|base64 -d 就能还原;真正的安全要靠 RBAC + 加密存储
  • 常用类型:Opaque(普通键值)、kubernetes.io/dockerconfigjson(私有仓库凭据)

8.2 方式一:–from-literal 键值

root@master30 configmap 14:59:34# kubectl create secret generic mysecret1 --from-literal=user=tom --from-literal=password1=redhat --from-literal=password2=redhat
secret/mysecret1 created
root@master30 configmap 15:21:00# kubectl get secret
NAME        TYPE     DATA   AGE
mysecret1   Opaque   3      7s

# 查看:值都是 base64 编码
root@master30 configmap 15:21:07# kubectl get secrets mysecret1 -o yaml
data:
  password1: cmVkaGF0
  password2: cmVkaGF0
  user: dG9t
type: Opaque

# 解码验证
root@master30 configmap 15:30:32# echo cmVkaGF0 | base64 -d
redhat
root@master30 configmap 15:33:02# echo dG9t | base64 -d
tom

补充:kubectl get secrets mysecret1 -o jsonpath={.data} | json_reformat 可以格式化查看(需 apt install -y json-glib-tools,Ubuntu 上 json_reformat 在 yajl-tools 里)。

8.3 方式二:–from-file 文件

# 把文件内容作为键值(键名 = 文件名)
root@master30 configmap 15:33:31# echo -n tom > user
root@master30 configmap 15:33:47# echo -n redhat > password1
root@master30 configmap 15:34:05# echo -n redhat > password2
root@master30 configmap 15:34:12# kubectl create secret generic mysecret2 --from-file=./user --from-file=./password2
secret/mysecret2 created

# 用 kubectl edit 补键(把 user/password2 之外再加 password1)
root@master30 configmap 15:35:15# kubectl edit secrets mysecret2
secret/mysecret2 edited
root@master30 configmap 15:37:59# kubectl describe secrets mysecret2
Type:  Opaque
Data
====
password1:  6 bytes
password2:  6 bytes
user:       3 bytes

8.4 方式三:–from-env-file 环境变量文件

# env.txt 每行一个"键=值"(一行写多个键值只算一个键,见第十章错误 8)
root@master30 configmap 15:41:15# vim env.txt
user=tom
password1=redhat
password2=redhat

root@master30 configmap 15:41:34# kubectl create secret generic mysecret4 --from-env-file=./env.txt
secret/mysecret4 created
root@master30 configmap 15:41:39# kubectl get secrets mysecret4
NAME        TYPE     DATA   AGE
mysecret4   Opaque   3      32s

8.5 方式四:docker-registry 私有仓库凭据

私有仓库需要登录才能拉镜像,把凭据做成 dockerconfigjson 类型的 Secret,Pod 里用 imagePullSecrets 引用:

root@master30 configmap 15:44:38# kubectl create secret docker-registry registry \
    --docker-username=laoma --docker-password=redhat \
    --docker-email=admin@laoma.cloud --docker-server=registry.laoma.cloud
secret/registry created

root@master30 configmap 15:44:38# kubectl get secrets registry -o yaml
data:
  .dockerconfigjson: eyJhdXRocyI6...
type: kubernetes.io/dockerconfigjson

8.6 实战:wordpress + mysql 双容器 Pod(bbs)

# wordpress.yaml:mysql 与 wordpress 同 Pod,wordpress 通过 127.0.0.1 连本 Pod 的 mysql
apiVersion: v1
kind: Pod
metadata:
  name: bbs
  labels:
    run: bbs
spec:
  containers:
  - name: mysql
    image: hub.laoma.cloud/library/mysql:latest
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "123"
    - name: MYSQL_USER
      value: "tom"
    - name: MYSQL_PASSWORD
      value: "123"
    - name: MYSQL_DATABASE
      value: bbs
    ports:
    - containerPort: 3306
  - name: wordpress
    image: hub.laoma.cloud/library/wordpress:latest
    env:
    - name: WORDPRESS_DB_USER
      value: tom
    - name: WORDPRESS_DB_PASSWORD
      value: "123"
    - name: WORDPRESS_DB_NAME
      value: bbs
    - name: WORDPRESS_DB_HOST
      value: 127.0.0.1
    ports:
    - containerPort: 80
root@master30 configmap 15:47:28# kubectl apply -f wordpress.yaml
pod/bbs created
root@master30 configmap 15:49:26# kubectl get pods -o wide
NAME        READY   STATUS    RESTARTS   AGE    IP              NODE
bbs         2/2     Running   0          117s   10.224.83.140   worker32.ningcode.cn

# 访问验证:返回 302 跳转到安装页 → wordpress 正常
root@master30 configmap 15:54:20# curl -v http://10.224.83.140
< HTTP/1.1 302 Found
< Server: Apache/2.4.66 (Debian)
< X-Powered-By: PHP/8.3.30
< Location: http://10.224.83.140/wp-admin/install.php

# 进容器(默认进第一个容器 mysql;多容器注意 -c 指定)
root@master30 configmap 15:55:31# kubectl exec -it bbs -- bash
Defaulted container "mysql" out of: mysql, wordpress
bash-5.1# exit

对比实验:单独部署 wordpress Pod(wordpress-1.yaml,不配数据库)后 curl 10.224.195.137 返回 Error establishing a database connection —— wordpress 必须能连上 mysql(同 Pod 用 127.0.0.1,跨 Pod 用 Service/ClusterIP)。

↑ 回到目录


九、控制器:ReplicaSet

9.1 概念

  • ReplicaSet(RS)负责维持指定数量的 Pod 副本:Pod 意外删除会自动重建(自愈)
  • 它是 Deployment 的底层组件,生产上通常直接使用 Deployment,但理解 RS 才能理解控制器原理
  • 工作方式:通过 selector.matchLabels 匹配并管理带有对应标签的 Pod

9.2 创建 ReplicaSet(rs.yaml)

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx
spec:
  replicas: 3                # 保持 3 个副本
  selector:
    matchLabels:
      app: nginx             # 管理带 app=nginx 标签的 Pod
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: hub.laoma.cloud/library/nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
root@master30 ~ 16:24:40# kubectl apply -f rs.yaml
replicaset.apps/nginx created
root@master30 ~ 16:24:49# kubectl get rs
NAME    DESIRED   CURRENT   READY   AGE
nginx   3         3         3       7s
root@master30 ~ 16:24:56# kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
nginx-2j75s   1/1     Running   0          61s
nginx-9fwrm   1/1     Running   0          61s
nginx-snrtj   1/1     Running   0          61s

# 确认 Pod 由谁管理
root@master30 ~ 16:26:10# kubectl describe pod nginx-2j75s | grep Controlled
Controlled By:  ReplicaSet/nginx

9.3 自愈演示:删除一个 Pod 自动补位

root@master30 ~ 16:26:14# kubectl delete pods nginx-2j75s
pod "nginx-2j75s" deleted
root@master30 ~ 16:26:33# kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
nginx-9fwrm   1/1     Running   0          109s
nginx-sl9tq   1/1     Running   0          6s      ← 新补位的 Pod
nginx-snrtj   1/1     Running   0          109s

数量始终回到 3 个,这就是"副本保持 + 自愈"。

9.4 删除策略:级联删除 vs 孤儿(orphan)

# 默认删除:RS 与它管理的 Pod 一起删除(级联)
root@master30 ~ 16:27:58# kubectl delete rs nginx
replicaset.apps "nginx" deleted
root@master30 ~ 16:28:26# kubectl get pods
No resources found in controllers namespace.

# 重新创建后演示 orphan:只删 RS,Pod 保留成为孤儿
root@master30 ~ 16:28:56# kubectl apply -f rs.yaml
replicaset.apps/nginx created
root@master30 ~ 16:29:22# kubectl delete rs nginx --cascade=orphan
replicaset.apps "nginx" deleted
root@master30 ~ 16:29:47# kubectl get pods          # 3 个 Pod 还在,但已无人管理
NAME          READY   STATUS    RESTARTS   AGE
nginx-cfxdj   1/1     Running   0          36s
nginx-kznck   1/1     Running   0          36s
nginx-ptx6g   1/1     Running   0          36s

# 手动清理这些孤儿 Pod
root@master30 ~ 16:30:22# kubectl delete pods --all
pod "nginx-cfxdj" deleted
pod "nginx-kznck" deleted
pod "nginx-ptx6g" deleted

kubectl delete rs nginx --cascade=orphan 场景:想保留 Pod 继续跑、同时停止 RS 的副本管理(例如滚动升级前临时摘除控制器)。

要点: RS 只保证"数量",不保证"版本升级";要实现滚动更新/回滚请用 Deployment(RS 之上再封装一层)。

↑ 回到目录


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

以下全部来自本次实验真实报错,按本文档操作即可避开。

10.1 命令语法类

错误示范报错正确写法
kubectl describe busyboxthe server doesn't have a resource type "busybox"kubectl describe pods busybox(带资源类型)
kubectl exec busybox -c busybox1 --touch /data/b1-f1unknown flag: --touchkubectl exec ... -- touch ...-- 后要有空格)
kubectl get pods -o wide -qunknown shorthand flag: 'q' in -q--no-headers 或不加 -q
curl http:10.224.83.140:80URL rejected: Port number was not a decimal numbercurl http://10.224.83.140:80// 不能少)
kubectl exec -it -- bashpod, type/name or --filename must be specifiedkubectl exec -it bbs -- bash(先写 Pod 名)
kubectl run pods nginx --image=...创建了一个名叫 pods 的多余 Podkubectl run <Pod名> --image=...,第一个参数是 Pod 名不是资源类型
kubectl apply -y / kubectl delete --allunknown shorthand flag: 'y' / at least one resource must be specifiedkubectl apply -f xxx.yaml / kubectl delete pods --all
kubectl delete allresource(s) were provided, but no name was specifiedkubectl delete pods --all(指定资源类型)

10.2 资源状态类

现象原因修复
Pod 卡 CreateContainerConfigErrorenv 引用的 ConfigMap 不存在kubectl create configmap mysql --from-literal=password=redhat 再建 Pod
Pod 一直 Pending,describe 报 persistentvolumeclaim "webclaim" not found顺序反了:先建了 Pod,PVC 还没创建先 apply pv.yaml → pvc.yaml → 再建 Pod
kubectl create cm webapp literal=index.html="..."缺少 --from-literal 前缀,且 NAME 写多kubectl create cm webapp-1 --from-literal=index.html="..." --from-literal=error.html="..."
Secret --from-env-file 只生成 1 个键env.txt 一行写了多个键值每行一个键:user=tom / password1=redhat / password2=redhat
haproxy curl Connection refused / 503 Service Unavailablebackend 填了错误 IPkubectl get pods -o wide 查真实 Pod IP,改 haproxy.cfg 后重建 ConfigMap 和 Pod
kubectl get secrets docker-registry-secret -o yaml → NotFoundSecret 名字是 registrykubectl get secrets registry -o yaml
单独 wordpress Pod 返回 Database Error establishing a database connectionwordpress 连不上 mysql与 mysql 同 Pod 时 WORDPRESS_DB_HOST=127.0.0.1;跨 Pod 需配置正确的 DB 地址
RS apply 报 namespaces "contoller" not foundnamespace 拼写错误(contoller)kubectl create ns controllers(正确拼写),再 set-context

10.3 概念要点

  • emptyDir 数据随 Pod 删除消失;hostPath 数据留在宿主机但不跨节点;NFS/PV-PVC 数据在 Pod 之外,Pod 删了还在
  • ConfigMap/Secret 挂载是只读的(readOnly: true),改配置需重建 ConfigMap 并重建 Pod 生效
  • Secret 的 base64 只是编码:echo cmVkaGF0 | base64 -d 即可还原,别把真密码写进 YAML 提交到 Git
  • ReplicaSet 只保数量不保版本;删除 RS 默认级联删 Pod,--cascade=orphan 保留 Pod

↑ 回到目录


十一、命令速查表

11.1 命名空间与上下文

命令用途
kubectl create ns storage / kubectl delete ns storage创建/删除命名空间
kubens storage切换当前命名空间
kubectl config set-context --current --namespace=storage等价切换
kubectl config get-contexts查看上下文与 namespace 列

11.2 存储

命令用途
kubectl apply -f pod-with-emptyDir.yamlemptyDir 示例
kubectl exec busybox -c busybox1 -- touch /data/b1-f1指定容器执行命令
kubectl get pv / kubectl get pvc查看 PV/PVC
kubectl apply -f pv.yamlpvc.yamlpod-with-pvc.yaml正确创建顺序
apt install -y nfs-kernel-server + 写 /etc/exports + systemctl restart nfs-server.service搭建 NFS 服务端

11.3 ConfigMap

命令用途
kubectl create cm mysql --from-literal=password=redhat键值创建
kubectl create cm web2 --from-file=./web2目录/文件创建
kubectl get cm web2 -o yaml查看内容
kubectl delete cm webapp-1 webapp-2删除多个

11.4 Secret

命令用途
kubectl create secret generic mysecret1 --from-literal=user=tom ...键值创建
kubectl create secret generic mysecret2 --from-file=./user文件创建
kubectl create secret generic mysecret4 --from-env-file=./env.txt环境变量文件创建
kubectl create secret docker-registry registry --docker-username=... --docker-server=...私有仓库凭据
echo cmVkaGF0 | base64 -d解码查看

11.5 控制器

命令用途
kubectl apply -f rs.yaml创建 ReplicaSet
kubectl get rs / kubectl get pods查看副本与 Pod
kubectl describe pod nginx-2j75s | grep Controlled确认控制器归属
kubectl delete rs nginx级联删除(Pod 一起删)
kubectl delete rs nginx --cascade=orphan只删 RS,Pod 保留
kubectl delete pods --all清空当前命名空间全部 Pod

小结

  1. 存储进阶路线:emptyDir(临时共享)→ hostPath(宿主机直连)→ NFS(跨节点共享)→ PV/PVC(存储与使用解耦),一层层解决"数据放哪、谁管理、怎么申请"的问题。
  2. PV/PVC 顺序:先 PV 再 PVC 再 Pod,顺序反了 Pod 会 Pending 并报 persistentvolumeclaim not found
  3. ConfigMap/Secret 双用途:既能注入环境变量(configMapKeyRef / secretKeyRef),也能挂载成文件;haproxy 配置放 ConfigMap 改起来非常方便,但改完要重建 Pod
  4. Secret 的坑:base64 不是加密;--from-env-file 必须每行一个键值;私有仓库拉镜像要用 docker-registry 类型 + imagePullSecrets
  5. ReplicaSet 是控制器入门:副本保持、自愈、级联/孤儿删除一次看懂,生产进阶再学 Deployment 的滚动更新。
  6. 排查口诀:先 describe 看 Events,再对第十章表格逐条对照,大部分报错都是语法/顺序/拼写问题。

本文档由 master30_2026-08-07_9_08_22.log 整理而来,命令提示符保留真实时间戳(root@master30 storage 10:07:56#),全部 YAML 与报错均来自实际操作。

更多推荐