一文弄懂什么是Kubernetes?什么是pod?
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 实验流程图
1.2 节点环境
| 节点 | 主机名 | IP | 角色 |
|---|---|---|---|
| master30 | master30.ningcode.cn | 10.1.8.30 | 控制平面(同时充当 NFS 服务端) |
| worker31 | worker31.ningcode.cn | 10.1.8.31 | 工作节点 |
| worker32 | worker32.ningcode.cn | 10.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 |
| ConfigMap | env 注入、volume 挂载、haproxy 动态配置 | 13:52–14:59 |
| Secret | base64 存储、四种创建方式、私有仓库拉镜像 | 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 busybox | the server doesn't have a resource type "busybox" | kubectl describe pods busybox(带资源类型) |
kubectl exec busybox -c busybox1 --touch /data/b1-f1 | unknown flag: --touch | kubectl exec ... -- touch ...(-- 后要有空格) |
kubectl get pods -o wide -q | unknown shorthand flag: 'q' in -q | 用 --no-headers 或不加 -q |
curl http:10.224.83.140:80 | URL rejected: Port number was not a decimal number | curl http://10.224.83.140:80(// 不能少) |
kubectl exec -it -- bash | pod, type/name or --filename must be specified | kubectl exec -it bbs -- bash(先写 Pod 名) |
kubectl run pods nginx --image=... | 创建了一个名叫 pods 的多余 Pod | kubectl run <Pod名> --image=...,第一个参数是 Pod 名不是资源类型 |
kubectl apply -y / kubectl delete --all | unknown shorthand flag: 'y' / at least one resource must be specified | kubectl apply -f xxx.yaml / kubectl delete pods --all |
kubectl delete all | resource(s) were provided, but no name was specified | kubectl delete pods --all(指定资源类型) |
10.2 资源状态类
| 现象 | 原因 | 修复 |
|---|---|---|
Pod 卡 CreateContainerConfigError | env 引用的 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 Unavailable | backend 填了错误 IP | 用 kubectl get pods -o wide 查真实 Pod IP,改 haproxy.cfg 后重建 ConfigMap 和 Pod |
kubectl get secrets docker-registry-secret -o yaml → NotFound | Secret 名字是 registry | kubectl get secrets registry -o yaml |
单独 wordpress Pod 返回 Database Error establishing a database connection | wordpress 连不上 mysql | 与 mysql 同 Pod 时 WORDPRESS_DB_HOST=127.0.0.1;跨 Pod 需配置正确的 DB 地址 |
RS apply 报 namespaces "contoller" not found | namespace 拼写错误(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.yaml | emptyDir 示例 |
kubectl exec busybox -c busybox1 -- touch /data/b1-f1 | 指定容器执行命令 |
kubectl get pv / kubectl get pvc | 查看 PV/PVC |
kubectl apply -f pv.yaml → pvc.yaml → pod-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 |
小结
- 存储进阶路线:emptyDir(临时共享)→ hostPath(宿主机直连)→ NFS(跨节点共享)→ PV/PVC(存储与使用解耦),一层层解决"数据放哪、谁管理、怎么申请"的问题。
- PV/PVC 顺序:先 PV 再 PVC 再 Pod,顺序反了 Pod 会 Pending 并报
persistentvolumeclaim not found。 - ConfigMap/Secret 双用途:既能注入环境变量(configMapKeyRef / secretKeyRef),也能挂载成文件;haproxy 配置放 ConfigMap 改起来非常方便,但改完要重建 Pod。
- Secret 的坑:base64 不是加密;
--from-env-file必须每行一个键值;私有仓库拉镜像要用docker-registry类型 +imagePullSecrets。 - ReplicaSet 是控制器入门:副本保持、自愈、级联/孤儿删除一次看懂,生产进阶再学 Deployment 的滚动更新。
- 排查口诀:先
describe看 Events,再对第十章表格逐条对照,大部分报错都是语法/顺序/拼写问题。
本文档由 master30_2026-08-07_9_08_22.log 整理而来,命令提示符保留真实时间戳(root@master30 storage 10:07:56#),全部 YAML 与报错均来自实际操作。
更多推荐
所有评论(0)