六、K8s管理
六、K8s管理
- 六、K8s管理
- 1、K8s配置管理:ConfigMap和Secret实践
- 2、K8s存储管理:数据持久化及动态存储
- 3、K8s任务管理:Job和CronJob实践
- 六、K8s管理
- 1、K8s配置管理:ConfigMap和Secret实践
- 2、K8s存储管理:数据持久化及动态存储
1、K8s配置管理:ConfigMap和Secret实践
1.1 ConfigMap介绍及资源定义
ConfigMap也是k8s的一种资源,主要用于存储配置数据,如程序的环境变量、配置文件等。ConfigMap可以实现把应用程序等配置信息从容器镜像或者代码中分离出来,从而可以更容易等管理和更新配置,而不必重新构建应用程序。
主要用途:
- 存储配置信息:剥离应用程序配置,实现配置分离
- 动态配置:动态更高程序的配置,无需重新部署整个应用
- 共享配置:数据共享,多个Pod可以共享ConfigMap数据
1.2 创建 ConfigMap
1.2.1 基于 yaml 文件创建
# 基于 ConfigMap 资源创建:
[root@k8s-master01 ~]# vim basic-cm.yaml
[root@k8s-master01 ~]# cat basic-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: basic-config
data:
key1: test01
key2: test02
[root@k8s-master01 ~]# kubectl create -f basic-cm.yaml
[root@k8s-master01 ~]# kubectl get cm
NAME DATA AGE
basic-config 2 18s
# 查看yaml配置
[root@k8s-master01 ~]# kubectl get cm basic-config -oyaml
apiVersion: v1
data: # key
key1: test01 # value
key2: test02
kind: ConfigMap
metadata:
creationTimestamp: "2025-06-16T14:26:29Z"
name: basic-config
namespace: default
resourceVersion: "9640"
uid: 10e39b92-1154-48b9-b046-877fa687fd19
# 基于多行value 创建:
[root@k8s-master01 ~]# vim cm-multiple.yaml
[root@k8s-master01 ~]# cat cm-multiple.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: multiple-config
data:
multiple.conf: |- # key
port: 6379 # 全是 multiple.conf的value
bind: 0.0.0.0 # 全是 multiple.conf的value
pass: PASSWORD # 全是 multiple.conf的value
[root@k8s-master01 ~]# kubectl create -f cm-multiple.yaml
[root@k8s-master01 ~]# kubectl get cm multiple-config
NAME DATA AGE
multiple-config 1 14s
查看数据:
[root@k8s-master01 ~]# kubectl describe cm multiple-config
Name: multiple-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
multiple.conf:
----
port: 6379
bind: 0.0.0.0
pass: PASSWORD
BinaryData
====
Events: <none>
1.2.2 基于文件创建
1、基于单个文件
# 创建测试文件
[root@k8s-master01 ~]# vim nginx.conf
[root@k8s-master01 ~]# cat nginx.conf
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
# kubectl create cm [cm名称] [--from-file=基于创建的文件]
[root@k8s-master01 ~]# kubectl create cm nginx.conf --from-file=nginx.conf
[root@k8s-master01 ~]# kubectl get cm nginx.conf
NAME DATA AGE
nginx.conf 1 9s
[root@k8s-master01 ~]# kubectl describe cm nginx.conf
Name: nginx.conf
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
nginx.conf:
----
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
BinaryData
====
Events: <none>
2、基于文件创建并更改 key 名:
[root@k8s-master01 ~]# kubectl create cm nginx.conf-02 --from-file=nginx.conf02=nginx.conf
[root@k8s-master01 ~]# kubectl get cm nginx.conf-02
NAME DATA AGE
nginx.conf-02 1 5s
[root@k8s-master01 ~]# kubectl describe cm nginx.conf-02
Name: nginx.conf-02 # cm的名称
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
nginx.conf02: # 自定义的key名称
----
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
BinaryData
====
Events: <none>
3、基于多个文件创建:
# 创建测试文件
[root@k8s-master01 ~]# echo "mysqld" > my.cnf
[root@k8s-master01 ~]# echo "Redis" > redis.conf
[root@k8s-master01 ~]# kubectl create cm multile-file --from-file=my.cnf --from-file=redis.conf
[root@k8s-master01 ~]# kubectl get cm multile-file
NAME DATA AGE
multile-file 2 7s # data数据显示2
# 查看数据
[root@k8s-master01 ~]# kubectl describe cm multile-file
Name: multile-file
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
my.cnf:
----
mysqld
redis.conf:
----
Redis
BinaryData
====
Events: <none>
1.2.3 基于文件夹创建
# 创建测试文件夹
[root@k8s-master01 ~]# mkdir conf
[root@k8s-master01 ~]# mv my.cnf redis.conf ./conf
[root@k8s-master01 ~]# ls ./conf/
my.cnf redis.conf
[root@k8s-master01 ~]# kubectl create cm dirconfig --from-file=./conf
[root@k8s-master01 ~]# kubectl get cm dirconfig
NAME DATA AGE
dirconfig 2 5s
# 查看数据
[root@k8s-master01 ~]# kubectl describe cm dirconfig
Name: dirconfig
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
my.cnf:
----
mysqld
redis.conf:
----
Redis
BinaryData
====
Events: <none>
1.2.4 基于环境变量创建
创建 ConfigMap 时,可以通过--from-env-file 创建环境变量类型的 ConfigMap。创建环境变量类型的 ConfigMap,只需要在文件内写入 KEY=VALUE 格式的内容即可创建:
# 创建环境变量文件
[root@k8s-master01 ~]# vim env-file
[root@k8s-master01 ~]# cat env-file
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASS=password
[root@k8s-master01 ~]# kubectl create cm env-cm --from-env-file=env-file
[root@k8s-master01 ~]# kubectl get cm env-cm
NAME DATA AGE
env-cm 4 6s # 显示4个变量数据
# 一共四个key对
[root@k8s-master01 ~]# kubectl describe cm env-cm
Name: env-cm
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
DB_HOST: # key
----
localhost # value
DB_PASS: # key
----
password # value
DB_PORT: # key
----
3306 # value
DB_USER: # key
----
root # value
BinaryData
====
Events: <none>
1.2.5 基于 literal 创建
# Literal 一般用于创建环境变量形式的 ConfigMap:
[root@k8s-master01 ~]# kubectl create configmap example-config --from-literal=key1=config1 --from-literal=key2=config2
[root@k8s-master01 ~]# kubectl get cm example-config
NAME DATA AGE
example-config 2 10s
[root@k8s-master01 ~]# kubectl describe cm example-config
Name: example-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
key1:
----
config1
key2:
----
config2
BinaryData
====
Events: <none>
1.3 更新 ConfigMap
# 基于 Yaml 文件修改
kubectl replace -f basic-cm.yaml
# 直接通过 edit 修改
kubectl edit cm nginx-conf
# 通过源文件修改
1、首先修改本地文件:
vim nginx.conf
2、先转成 yaml 再进行 replace
kubectl create cm nginx-conf --from-file=nginx.conf=nginx.conf --dry-run=client -oyaml | kubectl replace -f -
1.4 挂载 ConfigMap
1.4.1 以文件形式挂载
# 创建一个模版并修改yaml文件
[root@k8s-master01 ~]# kubectl create deploy nginx --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15 --dry-run=client -oyaml > nginx.cm.yaml
# 修改cm挂载yaml文件
[root@k8s-master01 ~]# vim nginx.cm.yaml
[root@k8s-master01 ~]# cat nginx.cm.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
volumes:
- name: conf
configMap:
name: multile-file
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: conf
mountPath: /mnt
[root@k8s-master01 ~]# kubectl create -f nginx.cm.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-868bcb4db4-8shkf 1/1 Running 0 5s
# 查看挂在详情,默认情况下会以 ConfigMap 中的 Key 名作为挂载的文件名
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-868bcb4db4-8shkf -- bash
root@nginx-cm-868bcb4db4-8shkf:/# ls /mnt/
my.cnf redis.conf
1.4.2 指定挂载的文件名
[root@k8s-master01 ~]# vim nginx.items.yaml
[root@k8s-master01 ~]# vim nginx.items.yaml
[root@k8s-master01 ~]# cat nginx.items.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
volumes:
- name: conf
configMap:
name: multile-file
items: # 挂载 ConfigMap 时,可以指定挂载的文件名字
- key: my.cnf # 源名字
path: mysql.conf # 重新起的名字
optional: true # 如果上述key书写有误就可导致pod一直处于ContainerCreating状态。可以添加 optional 字段,把 key 必须存在改为非必须
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: conf
mountPath: /mnt
[root@k8s-master01 ~]# kubectl create -f nginx.items.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-85c4759ff7-tp4gm 1/1 Running 0 7s
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-85c4759ff7-tp4gm -- bash
# 发现就只剩下一个key。这是items一个局限性,只会挂载 items 配置的 key。如果不想key缺失,就必须在items下面把俩key全写上
root@nginx-cm-85c4759ff7-tp4gm:/# ls /mnt/
mysql.conf
1.4.3 自定义挂载权限
[root@k8s-master01 ~]# vim nginx.chmod.yaml
[root@k8s-master01 ~]# cat nginx.chmod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
volumes:
- name: conf
configMap:
name: multile-file
items:
- key: my.cnf
path: mysql.conf
- key: redis.conf
path: redis.conf-2
mode: 0777 # 局部权限,对单个 item 配置权限,八进制
defaultMode: 420 # 默认权限,对所有的 item 生效,十进制
# 权限支持八进制和十进制,任意选择即可
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: conf
mountPath: /mnt
[root@k8s-master01 ~]# kubectl create -f nginx.chmod.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-86dd9b4cd5-qjvbl 1/1 Running 0 3s
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-86dd9b4cd5-qjvbl -- bash
# 实际上挂载的文件是被软连接过去的
root@nginx-cm-86dd9b4cd5-qjvbl:/# ls -l /mnt/
total 0
lrwxrwxrwx 1 root root 17 Jun 11 11:56 mysql.conf -> ..data/mysql.conf
lrwxrwxrwx 1 root root 19 Jun 11 11:56 redis.conf-2 -> ..data/redis.conf-2
# 我们要查看它的源文件
root@nginx-cm-86dd9b4cd5-qjvbl:/# ls -l /mnt/..data/
total 8
-rw-r--r-- 1 root root 7 Jun 11 11:56 mysql.conf # 全局权限644已生效
-rwxrwxrwx 1 root root 6 Jun 11 11:56 redis.conf-2 # 局部权限777已生效。如果同时设置了局部权限和全局权限,局部权限要高于全局权限
1.4.4 文件挂载覆盖的问题
[root@k8s-master01 ~]# vim nginx.mount.yaml
[root@k8s-master01 ~]# cat nginx.mount.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
volumes:
- name: conf
configMap:
name: nginx.conf
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: conf
mountPath: /etc/nginx # 挂载目录
[root@k8s-master01 ~]# kubectl create -f nginx.mount.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-8449fbc4bd-mjbwn 0/1 CrashLoopBackOff 3 (<invalid> ago) 79s
# ConfigMap 和 Secret 挂载时,会直接覆盖原目录的内容,所以在挂载时需要注意。
# 比如需要挂载 nginx.conf 时,如果直接挂载至/etc/nginx 目录,会导致 nginx 无法启动:
[root@k8s-master01 ~]# kubectl logs -f nginx-cm-8449fbc4bd-mjbwn
2025/06/11 12:51:36 [emerg] 1#1: open() "/etc/nginx/mime.types" failed (2: No such file or directory) in /etc/nginx/nginx.conf:14
nginx: [emerg] open() "/etc/nginx/mime.types" failed (2: No such file or directory) in /etc/nginx/nginx.conf:14
[root@k8s-master01 ~]# cat nginx.mount.yaml
....
name: nginx
volumeMounts:
- name: conf
mountPath: /etc/nginx/nginx.conf # 指定挂载具体的文件路径
subPath: nginx.conf # 指定挂载具体的文件
[root@k8s-master01 ~]# kubectl create -f nginx.mount.yaml
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-cm-5486dcb56d-6ndxg 1/1 Running 0 3s
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-5486dcb56d-6ndxg -- bash
root@nginx-cm-5486dcb56d-6ndxg:/# ls /etc/nginx/
conf.d koi-utf mime.types nginx.conf uwsgi_params
fastcgi_params koi-win modules scgi_params win-utf
1.4.5 以环境变量形式挂载
[root@k8s-master01 ~]# kubectl get cm env-cm -oyaml
apiVersion: v1
data:
DB_HOST: localhost
DB_PASS: password
DB_PORT: "3306"
DB_USER: root
....
1、挂载指定的环境变量:
[root@k8s-master01 ~]# vim nginx.mv.yaml
[root@k8s-master01 ~]# cat nginx.mv.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
env: # 支持多组
- name: MYSQL_HOST # 变量的名称
valueFrom:
configMapKeyRef:
name: env-cm # 从哪个 ConfigMap 取的变量
key: DB_HOST # ConfigMap里的key
- name: MYSQL_PASS
valueFrom:
configMapKeyRef:
name: env-cm
key: DB_PASS
# 注意:Key 不存在同样会导致 Pod 一直处于 CreateContainerConfigError 状态
[root@k8s-master01 ~]# kubectl create -f nginx.mv.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-fb6fb686-j8jxz 1/1 Running 0 3s
# 进入容器查看变量已经生效
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-fb6fb686-j8jxz -- bash
root@nginx-cm-fb6fb686-j8jxz:/# env|grep MYSQL
MYSQL_HOST=localhost
MYSQL_PASS=password
2、批量生成环境变量:
[root@k8s-master01 ~]# vim nginx.mv.yaml
[root@k8s-master01 ~]# cat nginx.mv.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
envFrom:
- configMapRef:
name: env-cm # 从哪个 ConfigMap 取的变量
prefix: CM_ # 批量生成不支持修改变量名,但是支持增加一个前缀
[root@k8s-master01 ~]# kubectl create -f nginx.mv.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-5d75d59887-f52h4 1/1 Running 0 2s
# 进入容器查看变量已经生效,且增加了一个前缀
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-5d75d59887-f52h4 -- bash
root@nginx-cm-5d75d59887-f52h4:/# env | grep CM_
CM_DB_USER=root
CM_DB_HOST=localhost
CM_DB_PORT=3306
CM_DB_PASS=password
1.5 Secret介绍及资源定义
和ConfigMap类似,Secret也是k8s的一种资源,同样可以存储配置数据。和ConfigMap不同的是Secret提供了一种相对安全的方式来管理这些数据。同时相当于ConfigMap,Secret也有不同的类型,不同的类型具有不同的使用场景。比如Docker Registry Secret可以用了管理镜像仓库的用户名密码。
主要用途:
- 存储敏感信息:可以存储密码、Key等信息
- 数据保护:Secret的数据可以被加密存储,防止未经授权的访问
- 动态配置:动态更高程序的配置,无需重新部署整个应用
- 共享配置:数据共享,多个Pod可以共享ConfigMap数据
Secret常见类型
- Opaque:通用型Secret默认类型
- kubernetes.io/dockerconfigjson:下载私有仓库镜像使用的Secret,和宿主机的/root/.docker/config.json 一致,宿主机登陆后即可产生该文件
- kubernetes.io/tls:用于存储HTTPS域名证书文件的Secret,可以被Ingress使用
- 其他不常用类型:bootstrap.kubernetes.io/token、kubernetes.io/basic-auth、kubernetes.io/ssh-auth、kubernetes.io/service-account-token
1.6 创建 Secret
1.6.1 使用 literal 创建
# 可以使用 literal 创建,创建后会自动加密
# kubectl create secret [类型] [名称] --from-literal=[加密内容]
[root@k8s-master01 ~]# kubectl create secret generic basic-auth-secret --from-literal=username=admin --from-literal=password=securepassword
secret/basic-auth-secret created
# 查看创建的 Secret 资源:
[root@k8s-master01 ~]# kubectl get secret
NAME TYPE DATA AGE
basic-auth-secret Opaque 2 18s
[root@k8s-master01 ~]#
[root@k8s-master01 ~]# kubectl get secret basic-auth-secret -oyaml
apiVersion: v1
data:
password: c2VjdXJlcGFzc3dvcmQ= # 加密后的数据
username: YWRtaW4= # 加密后的数据
kind: Secret
metadata:
creationTimestamp: "2025-06-16T16:35:34Z"
name: basic-auth-secret
namespace: default
resourceVersion: "21360"
uid: 8e3a2a40-e234-4863-8b63-e7e65d5ed7a9
type: Opaque
# 解密
[root@k8s-master01 ~]# echo "c2VjdXJlcGFzc3dvcmQ=" | base64 -d
securepassword
[root@k8s-master01 ~]# echo "YWRtaW4=" | base64 -d
admin
1.6.2 基于 Yaml 文件创建
基于 yaml 文件形式创建,有两种方式:
1、第一种直接写在 data 下,需要对数据进行先加密:
# 要对数据进行先加密
[root@k8s-master01 ~]# echo "dar3adada" | base64
ZGFyM2FkYWRhCg==
[root@k8s-master01 ~]# vim secret.data.yaml
[root@k8s-master01 ~]# cat secret.data.yaml
apiVersion: v1
data:
ssh-key: ZGFyM2FkYWRhCg== # 加密后的数据
kind: Secret
metadata:
name: secret1
namespace: default
type: Opaque
[root@k8s-master01 ~]# kubectl create -f secret.data.yaml
[root@k8s-master01 ~]# kubectl get secret secret1
NAME TYPE DATA AGE
secret1 Opaque 1 9s
# 查看
[root@k8s-master01 ~]# kubectl get secret secret1 -oyaml
apiVersion: v1
data:
ssh-key: ZGFyM2FkYWRhCg==
kind: Secret
metadata:
creationTimestamp: "2025-06-16T16:43:57Z"
name: secret1
namespace: default
resourceVersion: "22114"
uid: 2ae95461-321c-49c6-b1cb-c47a70c6ecf1
type: Opaque
2、第二种是写在 stringData 下,无需进行先加密:
[root@k8s-master01 ~]# vim secret.stringdata.yaml
[root@k8s-master01 ~]# cat secret.stringdata.yaml
apiVersion: v1
stringData:
ssh-key: password # 直接写在 stringData 下,无需进行先加密
kind: Secret
metadata:
name: secret2
namespace: default
type: Opaque
[root@k8s-master01 ~]# kubectl create -f secret.stringdata.yaml
[root@k8s-master01 ~]# kubectl get secret secret2
NAME TYPE DATA AGE
secret2 Opaque 1 14s
[root@k8s-master01 ~]# kubectl get secret secret2 -oyaml
apiVersion: v1
data:
ssh-key: cGFzc3dvcmQ= # 数据已经加密
kind: Secret
metadata:
creationTimestamp: "2025-06-16T16:48:29Z"
name: secret2
namespace: default
resourceVersion: "22523"
uid: 073b81ac-ee11-41e5-8878-d1576774cc31
type: Opaque
1.6.3 基于文件和目录创建
1、基于文件创建
# env-file文件内容
[root@k8s-master01 ~]# cat env-file
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASS=password
# 基于文件创建和 ConfigMap 类似:
[root@k8s-master01 ~]# kubectl create secret generic env --from-file=env-file
[root@k8s-master01 ~]# kubectl get secret env
NAME TYPE DATA AGE
env Opaque 1 10s
[root@k8s-master01 ~]# kubectl get secret env -oyaml
apiVersion: v1
data:
env-file: REJfSE9TVD1sb2NhbGhvc3QKREJfUE9SVD0zMzA2CkRCX1VTRVI9cm9vdApEQl9QQVNTPXBhc3N3b3JkCg== # 数据已经被加密
kind: Secret
metadata:
creationTimestamp: "2025-06-16T16:51:57Z"
name: env
namespace: default
resourceVersion: "22834"
uid: ccbc63b5-2dc3-4e6a-a664-f67d41a51786
type: Opaque
2、基于目录创建
# 查看目录下的文件
[root@k8s-master01 ~]# ls conf/
my.cnf redis.conf
# 基于目录创建和 ConfigMap 类似:
[root@k8s-master01 ~]# kubectl create secret generic conf --from-file=./conf
[root@k8s-master01 ~]# kubectl get secret conf
NAME TYPE DATA AGE
conf Opaque 2 7s
[root@k8s-master01 ~]# kubectl get secret conf -oyaml
apiVersion: v1
data:
my.cnf: bXlzcWxkCg== # 数据已被加密
redis.conf: UmVkaXMK # 数据已被加密
kind: Secret
metadata:
creationTimestamp: "2025-06-16T16:54:32Z"
name: conf
namespace: default
resourceVersion: "23067"
uid: 7f344d9c-88bf-4913-825b-5b019623f55a
type: Opaque
1.6.4 使用 Secret 管理镜像仓库密钥
# 如果需要拉取私有仓库的镜像,需要给 deploy 等资源配置镜像仓库的密钥,此时可以先创建镜像仓库的密钥:
[root@k8s-master01 ~]# kubectl create secret docker-registry myregistrykey --docker-server=v1.0 --docker-username=ywb --docker-password=dot --docker-email=173@99.com
[root@k8s-master01 ~]# kubectl get secret myregistrykey
NAME TYPE DATA AGE
myregistrykey kubernetes.io/dockerconfigjson 1 12s
[root@k8s-master01 ~]# kubectl get secret myregistrykey -oyaml
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJ2MS4wIjp7InVzZXJuYW1lIjoieXdiIiwicGFzc3dvcmQiOiJkb3QiLCJlbWFpbCI6IjE3M0A5OS5jb20iLCJhdXRoIjoiZVhkaU9tUnZkQT09In19fQ==
kind: Secret
metadata:
creationTimestamp: "2025-06-16T17:00:46Z"
name: myregistrykey
namespace: default
resourceVersion: "23630"
uid: 7ad602c9-8191-40df-85b5-c11ce579a0fa
type: kubernetes.io/dockerconfigjson
# 解析下数据,看下格式
[root@k8s-master01 ~]# echo "eyJhdXRocyI6eyJ2MS4wIjp7InVzZXJuYW1lIjoieXdiIiwicGFzc3dvcmQiOiJkb3QiLCJlbWFpbCI6IjE3M0A5OS5jb20iLCJhdXRoIjoiZVhkaU9tUnZkQT09In19fQ==" | base64 -d
{"auths":{"v1.0":{"username":"ywb","password":"dot","email":"173@99.com","auth":"eXdiOmRvdA=="}}}
- docker-registry:指定 Secret 的类型
- myregistrykey: Secret 名称
- DOCKER_REGISTRY_SERVER:镜像仓库地址
- OCKER_USER:镜像仓库用户名,需要有拉取镜像的权限
- DOCKER_PASSWORD:镜像仓库密码
- OCKER_EMAIL:邮箱信息,可以为空
之后在 Pod 字段下添加该 Secret 即可:
spec:
imagePullSecrets:
- name: myregistrykey
containers:
1.6.5 使用 Secret 管理域名证书
Secret 可以使用 kubernetes.io/tls 类型管理域名的证书,然后用于 Ingress:
# 生成测试证书
[root@k8s-master01 ~]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=test.com"
# 创建 Secret
[root@k8s-master01 ~]# kubectl -n default create secret tls nginx-test-tls --key=tls.key --cert=tls.crt
[root@k8s-master01 ~]# kubectl get secret nginx-test-tls
NAME TYPE DATA AGE
nginx-test-tls kubernetes.io/tls 2 79s
# 之后即可在 Ingress 中使用:
tls:
- secretName: nginx-test-tls
1.7 挂载 Secret
1.7.1 以文件形式挂载
[root@k8s-master01 ~]# vim nginx.se.yaml
[root@k8s-master01 ~]# cat nginx.se.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-se
name: nginx-se
spec:
replicas: 1
selector:
matchLabels:
app: nginx-se
template:
metadata:
labels:
app: nginx-se
spec:
volumes:
- name: conf
secret: # 和 ConfigMap 类似,修改类型
secretName: basic-auth-secret # 名字跟ConfigMap不一致
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: conf
mountPath: /mnt
[root@k8s-master01 ~]# kubectl create -f nginx.se.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-se-6d8c867-bnb9p 1/1 Running 0 13s
# 进入容器查看(挂载后,文件内容会被解密)
[root@k8s-master01 ~]# kubectl exec -it nginx-se-6d8c867-bnb9p -- bash
root@nginx-se-6d8c867-bnb9p:/# cat /mnt/username
admin
root@nginx-se-6d8c867-bnb9p:/# cat /mnt/password
securepassword
1.7.2 以环境变量形式挂载
Secret 以环境变量形式挂载和 ConfigMap 类似,只是一些字段上的差异:
[root@k8s-master01 ~]# vim nginx.mount.yaml
[root@k8s-master01 ~]# cat nginx.mount.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-se
name: nginx-se
spec:
replicas: 1
selector:
matchLabels:
app: nginx-se
template:
metadata:
labels:
app: nginx-se
spec:
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
env: # 挂载指定的环境变量
- name: USERNAME
valueFrom:
secretKeyRef:
name: basic-auth-secret # secret名称
key: username # secret的key
envFrom: # 批量生成环境变量
- prefix: SECRET_ # 生成前缀,可选
secretRef:
name: basic-auth-secret # secret名称
[root@k8s-master01 ~]# kubectl create -f nginx.mount.yaml
deployment.apps/nginx-se created
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-se-7cb9899b7d-75nmf 1/1 Running 0 2s
[root@k8s-master01 ~]#
[root@k8s-master01 ~]# kubectl exec -it nginx-se-7cb9899b7d-75nmf --bash
error: unknown flag: --bash
See 'kubectl exec --help' for usage.
[root@k8s-master01 ~]# kubectl exec -it nginx-se-7cb9899b7d-75nmf -- bash
root@nginx-se-7cb9899b7d-75nmf:/# env | grep USERNAME
USERNAME=admin
root@nginx-se-7cb9899b7d-75nmf:/# env | grep SECRET_
SECRET_password=securepassword
SECRET_username=admin
1.7.3 使用 Secret 拉取私有仓库的镜像
假设有个镜像在私有仓库中,未使用账号密码是无法拉取镜像的,此时可以在部署资源中,添加 docker-registry 类型的 Secret。
假设创建一个 deployment,镜像所在的位置是私有仓库,如果未配置密钥,则会有如下报
错:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 35s default-scheduler Successfully assigned default/nginx-se-587f485dd6-6nshf to k8s-node01
Normal Pulling 34s (x2 over 46s) kubelet Pulling image "192.168.200.53/taobao/nginx:1.15"
Warning Failed 34s (x2 over 46s) kubelet Failed to pull image "192.168.200.53/taobao/nginx:1.15": failed to pull and unpack image "192.168.200.53/taobao/nginx:1.15": failed to resolve reference "192.168.200.53/taobao/nginx:1.15": pull access denied, repository does not exist or may require authorization: authorization failed: no basic auth credentials
Warning Failed 34s (x2 over 46s) kubelet Error: ErrImagePull
Normal BackOff 21s (x3 over 46s) kubelet Back-off pulling image "192.168.200.53/taobao/nginx:1.15"
Warning Failed 21s (x3 over 46s) kubelet Error: ImagePullBackOff
# 添加 Secret:
[root@k8s-master01 ~]# kubectl create secret docker-registry harbor --docker-server=192.168.200.53 --docker-username=taobao --docker-password=Taobao12345 --docker-email=taobao@qq.com
secret/harbor created
[root@k8s-master01 ~]# kubectl get secret harbor
NAME TYPE DATA AGE
harbor kubernetes.io/dockerconfigjson 1 9s
[root@k8s-master01 ~]# vim nginx.ha.yaml
[root@k8s-master01 ~]# cat nginx.ha.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-se
name: nginx-se
spec:
replicas: 1
selector:
matchLabels:
app: nginx-se
template:
metadata:
labels:
app: nginx-se
spec:
containers:
- image: 192.168.200.53/taobao/nginx:1.15
name: nginx
imagePullSecrets:
- name: harbor # 登录密匙
[root@k8s-master01 ~]# kubectl create -f nginx.ha.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-se-664b6bd89b-vpqtf 1/1 Running 0 7s
1.8 ConfigMap 和 Secret 注意事项
- 需提前创建 ConfigMap 和 Secret
- ConfigMap 和 Secret 必须要和 Pod 或者是引用它资源在同一个命名空间
- 如果引用了某个 key,需要确保引用的 Key 必须存在
- envFrom、valueFrom 无法热更新环境变量,subPath 也是无法热更新的
- envFrom 配置环境变量,如果 key 是无效的,它会忽略掉无效的 key
- ConfigMap 和 Secret 最好不要太大
- ConfigMap 和 Secret 支持热更新,但是程序未必支持!
1.9 ConfigMap 和 Secret区别
两者都可以管理程序的配置文件,只是Secret采用base64加密value数据,并且Secret提供了更多的类型以支持不同的使用场景
2、K8s存储管理:数据持久化及动态存储
2.1 常见的存储需求
- 用户数据
- 共享数据
- 文件数据
- 程序数据
- 配置文件
- 日志文件
2.2 什么是 Volumes
k8s是Volumes是一个对存储资源的抽象,属于pod级别的一个配置字段。
Volumes在pod中绑定多个、多种的数据类型,比如NFS、NAS、CEPH等,这些绑定的数据,可以挂载到pod中的一个或多个容器中,从而实现容器的数据持久化和数据共享。

2.3 Volumes常用类型
- EmptyDir:临时目录,当pod从节点删除时,EmptyDir中的数据也会被删除,常用于临时数据存储,如缓存、中间计算结果、数据共享等
- HostPath:节点数据共享,HostPath可以让容器直接访问节点上的文件或者目录,常用于和节点共享数据
- ConfigMap&Secret:用于挂载ConfigMap和Secret到容器中
- Downward API:元数据挂载,主要用于容器访问pod的一些元数据,比如标签、命名空间等
- NFS&NAS:网络文件系统,主要挂载远程存储到容器中,实现跨主机的数据共享和持久化
- PVC:pv请求,k8s中的一类资源,用于配置多种不同的存储后端
2.4 EmptyDir
EmptyDir 是 Kubernetes 支持的临时存储功能,主要用于多个容器的数据共享,当 Pod 重建时,数据会被清空。EmptyDir 可以绑定主机上的硬盘和内存作为 Volume,比如把 emptyDir.medium字段设置为 Memory,就可以让 Kubernetes 使用 tmpfs(内存支持的文件系统)。虽然 tmpfs 非常快,但是设置的大小会被计入到 Container 的内存限制当中。
2.4.1 使用 Volumes 直接绑定存储
[root@k8s-master01 ~]# vim emptydir-deploy.yaml
[root@k8s-master01 ~]# cat emptydir-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: emptydir
name: emptydir
spec:
replicas: 2
selector:
matchLabels:
app: emptydir
template:
metadata:
labels:
app: emptydir
spec:
volumes:
- name: share-volume # 名字
emptyDir: {} # 挂载类型({}:磁盘挂载)
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: share-volume
mountPath: /opt # 挂载到容器的目录
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/redis:7.2.5
name: redis
volumeMounts:
- name: share-volume
mountPath: /mnt # 挂载到容器的目录
[root@k8s-master01 ~]# kubectl create -f emptydir-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-58b5997b8b-2hf8s 2/2 Running 0 4s
emptydir-58b5997b8b-rsjmx 2/2 Running 0 4s
# 进入到第一个pod的nginx容器中
[root@k8s-master01 ~]# kubectl exec -it emptydir-58b5997b8b-2hf8s -c nginx -- bash
# 往共享目录写入一个数据文件
root@emptydir-58b5997b8b-2hf8s:/# echo "123" > /opt/test
# 进入到第一个pod的redis容器中
[root@k8s-master01 ~]# kubectl exec -it emptydir-58b5997b8b-2hf8s -c redis -- bash
# 数据文件成功被共享
root@emptydir-58b5997b8b-2hf8s:/data# cat /mnt/test
123
# 进到容器中即可查看到挂载的目录:
root@emptydir-58b5997b8b-2hf8s:/data# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
/dev/mapper/rl_k8s--node02-root xfs 49G 5.9G 44G 12% /mnt
....
# 只会在同一个pod里面的容器就行共享
# 进入到第二个pod的redis容器中
[root@k8s-master01 ~]# kubectl exec -it emptydir-58b5997b8b-rsjmx -c redis -- bash
root@emptydir-58b5997b8b-rsjmx:/data# ls /mnt/
root@emptydir-58b5997b8b-rsjmx:/data#
2.4.2 使用内存类型 EmptyDir
[root@k8s-master01 ~]# vim emptydir-deploy.yaml
[root@k8s-master01 ~]# cat emptydir-deploy.yaml
....
spec:
volumes:
- name: share-volume
emptyDir:
medium: Memory # 使用内存作为 EmptyDir,只需要把 medium 改为 Memory 即可
containers:
....
# 重新加载配置
[root@k8s-master01 ~]# kubectl replace -f emptydir-deploy.yaml
# pod已经更新
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-54647cc6df-ctfgm 2/2 Running 0 4s
emptydir-54647cc6df-fcfms 2/2 Running 0 6s
# 进入容器
[root@k8s-master01 ~]# kubectl exec -it emptydir-54647cc6df-ctfgm -c nginx -- bash
# 查看挂载的内存目录
root@emptydir-54647cc6df-ctfgm:/# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
tmpfs tmpfs 3.5G 0 3.5G 0% /opt
....
2.4.3 EmptyDir 大小限制
两种类型的 EmptyDir 都支持限制卷的大小,只需要添加 sizeLimit 字段即可:
1、内存类型的 emptyDir 不会超出限制的大小,如不限制将会使用机器内存的最大值,或容器内存限制之和的最大值
[root@k8s-master01 ~]# vim emptydir-deploy.yaml
[root@k8s-master01 ~]# cat emptydir-deploy.yaml
....
spec:
volumes:
- name: share-volume
emptyDir:
medium: Memory
sizeLimit: 10Mi # 限制卷的大小
containers:
....
[root@k8s-master01 ~]# kubectl replace -f emptydir-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-78578cbc5f-8lfrk 2/2 Running 0 9s
emptydir-78578cbc5f-kqktp 2/2 Running 0 6s
[root@k8s-master01 ~]# kubectl exec -it emptydir-78578cbc5f-8lfrk -c nginx -- bash
# 挂载大小与配置的一致
root@emptydir-78578cbc5f-8lfrk:/# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
tmpfs tmpfs 10M 0 10M 0% /opt
...
# 模拟制造20M文件,显示空间不足,说明针对内存限制生效了
root@emptydir-78578cbc5f-8lfrk:/# cd /opt/
root@emptydir-78578cbc5f-8lfrk:/opt# dd if=/dev/zero of=./file bs=1M count=20
dd: error writing './file': No space left on device
11+0 records in
10+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0.00880329 s, 1.2 GB/s
root@emptydir-78578cbc5f-8lfrk:/opt# du -sh /opt/
10M /opt/
2、磁盘类型的 emptyDir,限制大小后不会显示具体限制的大小
2. 磁盘类型的超出最大限制时,Pod 将会变成 Completed 状态,同时将会创建一个Pod
[root@k8s-master01 ~]# vim emptydir-deploy.yaml
[root@k8s-master01 ~]# cat emptydir-deploy.yaml
....
spec:
volumes:
- name: share-volume
emptyDir:
sizeLimit: 10Mi # 对磁盘挂载限制10M
containers:
....
[root@k8s-master01 ~]# kubectl replace -f emptydir-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-75b4898bf8-bwflb 2/2 Running 0 5s
emptydir-75b4898bf8-pcjjr 2/2 Running 0 7s
[root@k8s-master01 ~]# kubectl exec -it emptydir-75b4898bf8-bwflb -c nginx -- bash
root@emptydir-75b4898bf8-bwflb:/# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
/dev/mapper/rl_k8s--node02-root xfs 49G 5.9G 44G 12% /opt
....
# 模拟制造20M文件,但是并没有报错
root@emptydir-75b4898bf8-bwflb:/# cd /opt/
root@emptydir-75b4898bf8-bwflb:/opt# dd if=/dev/zero of=./file bs=1M count=20
20+0 records in
20+0 records out
20971520 bytes (21 MB, 20 MiB) copied, 0.0296455 s, 707 MB/s
root@emptydir-75b4898bf8-bwflb:/opt# du -sh /opt/
20M /opt/
# 退出容器等待2分钟查看pod状态
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-75b4898bf8-9w5t7 2/2 Running 0 3s
emptydir-75b4898bf8-bwflb 0/2 ContainerStatusUnknown 1 2m17s
emptydir-75b4898bf8-pcjjr 2/2 Running 0 2m19s
2.5 hostPath
HostPath 类型的卷可将节点上的文件或目录挂载到 Pod 上,用于容器和节点之间的数据共享。
比如使用 hostPath 类型的卷,将主机的/data 目录挂载到 Pod 的/test-pd 目录:
[root@k8s-master01 ~]# vim emptydir-deploy.yaml
[root@k8s-master01 ~]# cat emptydir-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: emptydir
name: emptydir
spec:
replicas: 2
selector:
matchLabels:
app: emptydir
template:
metadata:
labels:
app: emptydir
spec:
volumes:
- name: data
hostPath: # 挂载类型
path: /data # 宿主机挂载路径,不存在自动创建
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: data
mountPath: /opt # 容器挂载路径
[root@k8s-master01 ~]# kubectl replace -f emptydir-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-5b5f58b7b5-n9bcc 1/1 Running 0 8s
emptydir-5b5f58b7b5-vfqlr 1/1 Running 0 6s
# 进入容器创建一个测试文件
[root@k8s-master01 ~]# kubectl exec -it emptydir-5b5f58b7b5-n9bcc -- bash
root@emptydir-5b5f58b7b5-n9bcc:/# echo "123" > /opt/test
# 退出容器发现宿主机并没有自动创建挂载文件
root@emptydir-5b5f58b7b5-n9bcc:/# exit
exit
[root@k8s-master01 ~]# ll /data
ls: cannot access '/data': No such file or directory
# 原来只会在pod所在节点创建,查看pod在哪个节点上面(k8s-node01)
[root@k8s-master01 ~]# kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
emptydir-5b5f58b7b5-n9bcc 1/1 Running 0 2m55s 192.168.85.215 k8s-node01 <none> <none>
emptydir-5b5f58b7b5-vfqlr 1/1 Running 0 2m53s 192.168.58.233 k8s-node02 <none> <none>
# 在k8s-node01节点发现自动创建了文件
[root@k8s-node01 ~]# ll /data/
total 4
-rw-r--r-- 1 root root 4 Jun 12 02:19 test
# 而且刚才在容器内测试的文件也同步了过来;同时再添加一个测试文件
[root@k8s-node01 ~]# cat /data/test
123
[root@k8s-node01 ~]# echo "gogo" >> /data/node
# 重新登陆容器查看,数据已经同步
[root@k8s-master01 ~]# kubectl exec -it emptydir-5b5f58b7b5-n9bcc -- bash
root@emptydir-5b5f58b7b5-n9bcc:/# ls /opt/
node test
hostPath 卷常用的 type(类型)如下:
- type 为空字符串:默认选项,意味着挂载 hostPath 卷之前不会执行任何检查。
- DirectoryOrCreate:如果给定的 path 不存在任何东西,那么将根据需要创建一个权限为 0755 的空目录,和 Kubelet 具有相同的组和权限。
- Directory:目录必须存在于给定的路径下。
- FileOrCreate:如果给定的路径不存储任何内容,则会根据需要创建一个空文件,权限设置为 0644,和 Kubelet 具有相同的组和所有权。
- File:文件,必须存在于给定路径中。
- Socket:UNIX 套接字,如某个程序的 socket 文件,必须存在于给定路径中。
- CharDevice:字符设备,如串行端口、声卡、摄像头等,必须存在于给定路径中,且只有 Linux 支持。
- BlockDevice:块设备,如硬盘等,必须存在于给定路径中,且只有 Linux 支持。
2.6 NFS/NAS
使用远程存储介质,可以实现跨主机容器之间的数据共享,比如使用 NFS、NAS、CEPH 等。
2.6.1 NFS 搭建
# 准备一台机器用于搭建 NFS(为了节省资源,之前拿之前的harbor机器来测试)
# 首先服务端安装 NFS:
[root@harbor ~]# yum install nfs-utils rpcbind -y
# 配置共享目录:
[root@harbor ~]# mkdir -p /data/nfs
# 允许哪个网段可以挂载共享目录,有什么权限
[root@harbor ~]# echo "/data/nfs/ 192.168.200.0/24(rw,sync,no_subtree_check,no_root_squash)" >> /etc/exports
# 加载 NFS 配置
[root@harbor ~]# exportfs -r
# 启动NFS
[root@harbor ~]# systemctl enable --now nfs-server rpcbind
# 客户端同样安装NFS(咱们这里所有k8s节点都装一下)
[root@k8s-master01 ~]# yum install nfs-utils rpcbind -y
# 启动NFS
[root@k8s-master01 ~]# systemctl enable --now nfs-server rpcbind
# 挂载测试
[root@k8s-master01 ~]# mount -t nfs 192.168.200.53:/data/nfs /mnt/
[root@k8s-master01 ~]# df -hT | grep nfs
192.168.200.53:/data/nfs nfs4 99G 6.0G 93G 7% /mnt
[root@k8s-master01 ~]# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
192.168.200.53:/data/nfs nfs4 99G 6.0G 93G 7% /mnt
2.6.2 挂载 NFS 类型的卷
[root@k8s-master01 ~]# vim nfs-deploy.yaml
[root@k8s-master01 ~]# cat nfs-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nfs
name: nfs
spec:
replicas: 2
selector:
matchLabels:
app: nfs
template:
metadata:
labels:
app: nfs
spec:
volumes:
- name: nfs-volume
nfs:
server: 192.168.200.53 # 挂载NFS的IP
path: /data/nfs # 挂载NFS的目录
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: nfs-volume
mountPath: /mnt # 挂载在容器的目录
[root@k8s-master01 ~]# kubectl create -f nfs-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nfs-56d768cd6f-6xpsc 1/1 Running 0 4s
nfs-56d768cd6f-gpx54 1/1 Running 0 4s
# 进入容器并添加一个测试文件
[root@k8s-master01 ~]# kubectl exec -it nfs-56d768cd6f-6xpsc -- bash
root@nfs-56d768cd6f-6xpsc:/# ls /mnt/
root@nfs-56d768cd6f-6xpsc:/# echo "123" > /mnt/test
# 到NFS服务器上面检查数据是否同步
[root@harbor ~]# ll /data/nfs/
total 4
-rw-r--r-- 1 root root 4 Jun 13 20:33 test
[root@harbor ~]# cat /data/nfs/test
123
# 添加一个测试文件
[root@harbor ~]# echo "ceshi" > /data/nfs/test02
# 容器内也已经同步
root@nfs-56d768cd6f-6xpsc:/# ls /mnt/
test test02
2.7 使用 PV 和 PVC 挂载存储
2.7.1 PV和PVC基本概念
PersistentVolume:简称PV,是由K8是管理员设置到存储,可以配置Ceph、NFS、GlusterFS等常用存储,相对于Volume配置,提供了更多等功能,比如生命周期等管理、大小的限制,同时也可以支持存储的动态分配。
PersistentVolumeClaim:简称PVC,是对存储PV的请求,表示需要什么类型的PV,并绑定该PV。如果某个服务需要挂载存储,只需要在Volumes中添加PVC类型的Volume即可,并且通常只需要指定PVC的名称即可,无需关心整个的存储配置细节。
2.7.2 PV和PVC存储管理架构

2.7.3 PV 访问策略
- ReadWriteOnce:可以被单节点以读写模式挂载,命令行中可以被缩写为 RWO
- ReadOnlyMany:可以被多个节点以只读模式挂载,命令行中可以被缩写为 ROX
- ReadWriteMany:可以被多个节点以读写模式挂载,命令行中可以被缩写为 RWX
- ReadWriteOncePod:只能被单个 Pod 以读写模式挂载,命令行中可以被缩写为 RWOP
常见存储支持的访问策略:
| Volume Plugin | ReadWriteOnce | ReadOnlyMany | ReadWriteMany | ReadWriteOncePod |
|---|---|---|---|---|
| AzureFile | ✓ | ✓ | ✓ | - |
| CephFS | ✓ | ✓ | ✓ | - |
| CSI | 取决于存储本身 | 取决于存储本身 | 取决于存储本身 | 取决于存储本身 |
| FC | ✓ | ✓ | - | - |
| FlexVolume | ✓ | ✓ | 取决于存储本身 | - |
| HostPath | ✓ | - | - | - |
| iSCSI | ✓ | ✓ | - | - |
| NFS | ✓ | ✓ | ✓ | - |
| RBD | ✓ | ✓ | - | - |
| VsphereVolume | ✓ | - | -(works whenPods arecollocated) | - |
| PortworxVolume | ✓ | - | ✓ | - |
2.7.4 PV 回收策略
- Retain:保留,该策略允许手动回收资源,当删除 PVC 时,PV 仍然存在,PV 被视为已释放,管理员可以手动回收卷。
- Recycle:回收,如果 Volume 插件支持,Recycle 策略会对卷执行 rm -rf 清理该 PV,并使其可用于下一个新的 PVC,但是本策略将来会被弃用,目前只有 NFS 和 HostPath 支持该策略。
- Delete:删除,如果 Volume 插件支持,删除 PVC 时会同时删除 PV,动态存储默认为Delete,目前支持 Delete 的存储后端包括 AWS EBS、GCE PD、Azure Disk、OpenStackCinder、Ceph 等。
如果更改回收策略,只需要通过 persistentVolumeReclaimPolicy 字段配置即可
2.7.5 HostPath 类型的 PV
# 创建 HostPath 类型的 PV
[root@k8s-master01 ~]# vim hostpath-pv.yaml
[root@k8s-master01 ~]# cat hostpath-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
labels:
type: local
name: hostpath-pv
spec:
storageClassName: hostpath # PV的类,一个特定类型的PV 只能绑定到特定类别的PVC
capacity: # 容量配置
storage: 10Gi
accessModes: # 该 PV 的访问模式
- ReadWriteOnce # 可以被单节点以读写模式挂载,命令行中可以被缩写为RWO
hostPath:
path: "/mnt"
# 创建该 PV
[root@k8s-master01 ~]# kubectl create -f hostpath-pv.yaml
persistentvolume/hostpath-pv created
# 查看 PV 的状态
[root@k8s-master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
hostpath-pv 10Gi RWO Retain Available hostpath <unset>
# pv的状态:
# Available:可用,没有被 PVC 绑定的空闲资源。
# Bound:已绑定,已经被 PVC 绑定。
# Released:已释放,PVC 被删除,但是资源还未被重新使用。
# Failed:失败,自动回收失败
2.7.6 NFS/NAS 类型的 PV
# 创建一个 NAS/NFS 类型的 PV
[root@k8s-master01 ~]# vim nfs-pv.yaml
[root@k8s-master01 ~]# cat nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs
spec:
capacity: # 容量配置
storage: 5Gi
volumeMode: Filesystem # 卷的模式,目前支持 Filesystem(文件系统) 和 Block(块),其中 Block类型需要后端存储支持,默认为文件系统
accessModes: # 该 PV 的访问模式
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle # 回收策略
storageClassName: nfs-slow # PV的类,一个特定类型的PV只能绑定到特定类别的 PVC
nfs:
server: 192.168.200.53 # 挂载NFS的IP
path: /data/nfs # 挂载NFS的目录
# 创建该pv
[root@k8s-master01 ~]# kubectl create -f nfs-pv.yaml
# 查看 PV 的状态
[root@k8s-master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
....
pv-nfs 5Gi RWX Recycle Available nfs-slow <unset> 34s
2.7.7 创建 PVC 绑定 PV
# 创建一个 PVC 绑定到 NFS 的 PV 上
[root@k8s-master01 ~]# vim pvc-nfs.yaml
[root@k8s-master01 ~]# cat pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: task-pvc-claim # PVC的名称
spec:
storageClassName: nfs-slow # 想要从哪个pv绑定
accessModes: # 该 PV 的访问模式
- ReadWriteMany # 可以被多个节点以读写模式挂载,命令行中可以被缩写为RWX
resources:
requests:
storage: 3Gi # 能挂载的磁盘大小不能超过nfs-slow这个pv
# 创建该pvc
[root@k8s-master01 ~]# kubectl create -f pvc-nfs.yaml
persistentvolumeclaim/task-pvc-claim created
# 查看 PVC 的状态
[root@k8s-master01 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
task-pvc-claim Bound pv-nfs 5Gi RWX nfs-slow <unset> 3s
# 通过 PVC 把存储挂载到容器中
[root@k8s-master01 ~]# vim nginx-pvc.yaml
[root@k8s-master01 ~]# cat nginx-pvc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-pvc
name: nginx-pvc
spec:
replicas: 2
selector:
matchLabels:
app: nginx-pvc
template:
metadata:
labels:
app: nginx-pvc
spec:
volumes:
- name: data
persistentVolumeClaim: # PVC
claimName: task-pvc-claim # 绑定哪个PVC
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: data
mountPath: /mnt
[root@k8s-master01 ~]# kubectl create -f nginx-pvc.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-pvc-9fc455d4f-nggqw 1/1 Running 0 19s
nginx-pvc-9fc455d4f-qp4xn 1/1 Running 0 19s
# 进入一个容器查看挂载详情
[root@k8s-master01 ~]# kubectl exec -it nginx-pvc-9fc455d4f-nggqw -- bash
root@nginx-pvc-9fc455d4f-nggqw:/# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
192.168.200.53:/data/nfs nfs4 99G 6.2G 93G 7% /mnt
....
# 并输入一个测试文件
root@nginx-pvc-9fc455d4f-nggqw:/# echo "123" > /mnt/test
# 从NFS节点查看共享目录
[root@habor ~]# ll /data/nfs/
total 4
-rw-r--r-- 1 root root 4 Jun 18 22:17 test
# 同时添加一个测试文件
[root@habor ~]# echo "gogo" > /data/nfs/ceshi
# 回到容器查看共享目录已经更新
root@nginx-pvc-9fc455d4f-nggqw:/# ls /mnt/
ceshi test
# 查看另外一个pod的容器内的共享目录也已经更新
[root@k8s-master01 ~]# kubectl exec -it nginx-pvc-9fc455d4f-qp4xn -- bash
root@nginx-pvc-9fc455d4f-qp4xn:/# ls /mnt/
ceshi test
2.7.8 PVC 创建和挂载失败的原因
- PVC 一直 Pending 的原因:
- PVC 的空间申请大小大于 PV 的大小
- PVC 的 StorageClassName 没有和 PV 的一致
- PVC 的 accessModes 和 PV 的不一致
- 请求的 PV 已被其他的 PVC 绑定
- 挂载 PVC 的 Pod 一直处于 Pending:
- PVC 没有创建成功/PVC 不存在
- PVC 和 Pod 不在同一个 Namespace
2.8 动态存储
2.8.1 什么是动态存储
动态存储可以在用户需要存储资源时自动创建和配置PV,而无需手动创建和配置PV。可以让存储资源的分配变得更加灵活,而且可以随着应用程序的需求变化而动态调整
2.8.2 动态存储工作原理
动态存储依赖 StorageClass 和 CSI(Container Storage Interface) 实现,当我们创建一个PVC时,通过 StorageClassName 指定动态存储的类,该类指向了不同的存储供应商,比如Ceph、NFS等,之后通过该类就可完成PV的自动创建。
2.8.3 什么时CSI和StorageClass
1、容器存储接口-CSI
CSI:(Container Storage InterFace)是一个标准化的存储接口,用于在容器环境中集成外部存储系统。它提供里一种统一的方式来集成各种存储系统,无论是云提供商的存储服务,还是自建的存储集群,都可以通过CSI对接到容器平台中。
在同一个K8s集群中,可以同时存在多个CSI对接不同到存储平台,之后可以通过StorageClass的provisioner字段声明该Class对接哪一种存储平台
2、存储类-StorageClass
StorageClass和IngressClass类似,用于定义存储资源的类别,可以使用内置的存储插件或者第三方的CSI驱动程序关联存储。
在同一个K8s集群中,可以同时存在多个StorageClass对接不同或相同的CSI,用于实现更多的动态存储需求场景。
2.8.4 安装 NFS/NAS CS
# 下载安装文件
[root@k8s-master01 ~]# git clone https://gitee.com/dukuan/csi-driver-nfs.git
# 安装 CSI
[root@k8s-master01 ~]# cd csi-driver-nfs/
[root@k8s-master01 csi-driver-nfs]# sed -i "s#registry.k8s.io#k8s.m.daocloud.io#g" deploy/v4.9.0/*.yaml
[root@k8s-master01 csi-driver-nfs]# ./deploy/install-driver.sh v4.9.0 local
# 查看 Pod 状态
[root@k8s-master01 ~]# kubectl get po -n kube-system -l app=csi-nfs-node
NAME READY STATUS RESTARTS AGE
csi-nfs-node-hf6b5 3/3 Running 2 (7m20s ago) 9m59s
csi-nfs-node-n7xl5 3/3 Running 1 (4m25s ago) 9m59s
csi-nfs-node-r6vv2 3/3 Running 1 9m59s
[root@k8s-master01 ~]# kubectl get po -n kube-system -l app=csi-nfs-controller
NAME READY STATUS RESTARTS AGE
csi-nfs-controller-5c9687ccf5-2j7s8 4/4 Running 2 (4m58s ago) 10m
# 查看 CSI
[root@k8s-master01 ~]# kubectl get csidriver
NAME ATTACHREQUIRED PODINFOONMOUNT STORAGECAPACITY TOKENREQUESTS REQUIRESREPUBLISH MODES AGE
nfs.csi.k8s.io false false false <unset> false Persistent 3m41s
2.8.5 创建 StorageClass
# 更改 StorageClass 配置:
[root@k8s-master01 csi-driver-nfs]# vim deploy/v4.9.0/storageclass.yaml
[root@k8s-master01 csi-driver-nfs]# cat deploy/v4.9.0/storageclass.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
server: 192.168.200.53 # 更改为NFS的IP地址
share: /data/nfs # NFS的挂载目录
# csi.storage.k8s.io/provisioner-secret is only needed for providing mountOptions in DeleteVolume
# csi.storage.k8s.io/provisioner-secret-name: "mount-options"
# csi.storage.k8s.io/provisioner-secret-namespace: "default"
reclaimPolicy: Delete
volumeBindingMode: Immediate
mountOptions:
- nfsvers=4.1
# 创建sc
[root@k8s-master01 csi-driver-nfs]# kubectl create -f deploy/v4.9.0/storageclass.yaml
# 查看sc
[root@k8s-master01 csi-driver-nfs]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-csi nfs.csi.k8s.io Delete Immediate false 6s
2.8.6 挂载测试
# 创建 PVC
[root@k8s-master01 csi-driver-nfs]# cat deploy/example/pvc-nfs-csi-dynamic.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs-dynamic
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-csi
[root@k8s-master01 csi-driver-nfs]# kubectl create -f deploy/example/pvc-nfs-csi-dynamic.yaml
# 查看 PVC 和 PV:
# 直接就是绑定的状态,对应的pv是一个随机的串
[root@k8s-master01 csi-driver-nfs]# kubectl get pvc pvc-nfs-dynamic
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
pvc-nfs-dynamic Bound pvc-7f79caa6-31d6-430f-8b42-15e04f7f52f3 10Gi RWX nfs-csi <unset> 8s
[root@k8s-master01 csi-driver-nfs]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
....
pvc-7f79caa6-31d6-430f-8b42-15e04f7f52f3 10Gi RWX Delete Bound default/pvc-nfs-dynamic nfs-csi <unset> 13s
# 此时会在 NFS 目录下创建一个共享目录
[root@habor ~]# ls /data/nfs/
pvc-7f79caa6-31d6-430f-8b42-15e04f7f52f3
[root@k8s-master01 csi-driver-nfs]# vim nfs-deploy.yaml
[root@k8s-master01 csi-driver-nfs]# cat nfs-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nfs-pvc
name: nfs-pvc
spec:
replicas: 1
selector:
matchLabels:
app: nfs-pvc
template:
metadata:
labels:
app: nfs-pvc
spec:
volumes:
- name: nfs
persistentVolumeClaim:
claimName: pvc-nfs-dynamic # 挂载的PVC
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
command:
- "/bin/bash"
- "-c"
- while true; do echo $(hostname) $(date) >> /mnt/outfile;sleep 5; done
volumeMounts:
- name: nfs
mountPath: /mnt
readOnly: false
# 启动
[root@k8s-master01 csi-driver-nfs]# kubectl create -f nfs-deploy.yaml
[root@k8s-master01 csi-driver-nfs]# kubectl get po
NAME READY STATUS RESTARTS AGE
nfs-pvc-64f7c5d487-4zgdd 1/1 Running 0 6s
nfs-pvc-64f7c5d487-97rlg 1/1 Running 0 6s
nfs-pvc-64f7c5d487-vvdww 1/1 Running 0 6s
# 在 NFS 节点的数据目录,即可查看到来自于不同示例写入的数据
[root@harbor ~]# tail -10f /data/pvc-7f79caa6-31d6-430f-8b42-15e04f7f52f3/outfile
nfs-pvc-64f7c5d487-6vbf7 Thu Jun 12 05:56:22 UTC 2025
nfs-pvc-64f7c5d487-6vbf7 Thu Jun 12 05:56:27 UTC 2025
nfs-pvc-64f7c5d487-97rlg Thu Jun 12 06:00:33 UTC 2025
nfs-pvc-64f7c5d487-vvdww Thu Jun 12 06:00:33 UTC 2025
nfs-pvc-64f7c5d487-4zgdd Thu Jun 12 05:59:57 UTC 2025
nfs-pvc-64f7c5d487-97rlg Thu Jun 12 06:00:38 UTC 2025
nfs-pvc-64f7c5d487-vvdww Thu Jun 12 06:00:38 UTC 2025
nfs-pvc-64f7c5d487-4zgdd Thu Jun 12 06:00:02 UTC 2025
nfs-pvc-64f7c5d487-97rlg Thu Jun 12 06:00:43 UTC 2025
....
# 删除pod,删除pvc
[root@k8s-master01 csi-driver-nfs]# kubectl delete deploy nfs-pvc
[root@k8s-master01 csi-driver-nfs]# kubectl delete pvc pvc-nfs-dynamic
# pv也会被自动删除
[root@k8s-master01 csi-driver-nfs]# kubectl get pv
No resources found
# 共享目录里的数据也会被删除
[root@harbor ~]# ll /data/nfs/
total 0
2.8.7 部署 MySQL 并持久化数据
[root@k8s-master01 ~]# vim mysql-pvc.yaml
[root@k8s-master01 ~]# cat mysql-pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-csi
# 创建一个 MySQL 的 PVC
[root@k8s-master01 ~]# kubectl create -f mysql-pvc.yaml
# 查看PVC
[root@k8s-master01 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
mysql-data Bound pvc-5f0e69a1-177c-4bcd-a61a-3ba8a49830c7 10Gi RWX nfs-csi <unset> 4s
# 查看PV
[root@k8s-master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-5f0e69a1-177c-4bcd-a61a-3ba8a49830c7 10Gi RWX Delete Bound default/mysql-data nfs-csi <unset> 8s
# 创建一个 MYSQL 的 Deployment
[root@k8s-master01 ~]# vim mysql-deploy.yaml
[root@k8s-master01 ~]# cat mysql-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: mysql
name: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
volumes:
- name: nfs
persistentVolumeClaim:
claimName: mysql-data
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/mysql:8.0.20
name: mysql
ports:
- name: tcp-3306
containerPort: 3306
protocol: TCP
env:
- name: MYSQL_ROOT_PASSWORD
value: password_123
volumeMounts:
- name: nfs
mountPath: /var/lib/mysql
livenessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 30
timeoutSeconds: 3
periodSeconds: 30
successThreshold: 1
failureThreshold: 2
readinessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 30
timeoutSeconds: 3
periodSeconds: 30
successThreshold: 1
failureThreshold: 2
imagePullPolicy: IfNotPresent
restartPolicy: Always
dnsPolicy: ClusterFirst
# 启动这个pod
[root@k8s-master01 ~]# kubectl create -f mysql-deploy.yaml
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-6b4dcc8748-bqpx7 1/1 Running 0 2m9s
# 有状态的服务不允许多个实例去访问同一个数据的
# 新起一个pod
[root@k8s-master01 ~]# kubectl scale deploy mysql --replicas=2
# 一直在报错
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-6b4dcc8748-bqpx7 1/1 Running 0 5m22s
mysql-6b4dcc8748-ll7sd 0/1 Running 0 90s
[root@k8s-master01 ~]# kubectl logs -f mysql-6b4dcc8748-ll7sd
....
2025-06-12T06:33:45.744663Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2025-06-12T06:33:47.197596Z 1 [ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
2025-06-12T06:33:48.211322Z 1 [ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
2025-06-12T06:33:49.232938Z 1 [ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
....
# 进入容器
[root@k8s-master01 ~]# kubectl exec -it mysql-6b4dcc8748-bqpx7 -- bash
# 登陆mysql创建一个库
root@mysql-6b4dcc8748-bqpx7:/# mysql -uroot
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
root@mysql-6b4dcc8748-bqpx7:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 32
Server version: 8.0.20 MySQL Community Server - GPL
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.05 sec)
mysql> create database test;
Query OK, 1 row affected (0.02 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
5 rows in set (0.01 sec)
# MySQL 启动后,会在数据目录初始化基础数据,此时可以在后端存储中看到
[root@harbor ~]# ls /data/nfs/pvc-5f0e69a1-177c-4bcd-a61a-3ba8a49830c7/
auto.cnf binlog.index client-cert.pem '#ib_16384_1.dblwr' ib_logfile0 '#innodb_temp' performance_schema server-cert.pem test
binlog.000001 ca-key.pem client-key.pem ib_buffer_pool ib_logfile1 mysql private_key.pem server-key.pem undo_001
binlog.000002 ca.pem '#ib_16384_0.dblwr' ibdata1 ibtmp1 mysql.ibd public_key.pem sys undo_002
# 删除pod并重新建立新的pod
[root@k8s-master01 ~]# kubectl delete deploy mysql
[root@k8s-master01 ~]# kubectl create -f mysql-deploy.yaml
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-6b4dcc8748-g9bxq 1/1 Running 0 50s
# 进入到新容器发现之前创建到库还在
[root@k8s-master01 ~]# kubectl exec -it mysql-6b4dcc8748-g9bxq -- bash
root@mysql-6b4dcc8748-g9bxq:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 8.0.20 MySQL Community Server - GPL
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
5 rows in set (0.01 sec)
mysql>
2.8.8 部署 Redis 并持久化数据
# 创建一个 Redis 的 PVC
[root@k8s-master01 ~]# vim redis-pvc.yaml
[root@k8s-master01 ~]# cat redis-pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-data
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-csi
# 创建一个 Redis 的 PVC
[root@k8s-master01 ~]# kubectl create -f redis-pvc.yaml
# 查看PVC
[root@k8s-master01 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
redis-data Bound pvc-8494b4c2-2341-4bc8-aeda-8edfa9713139 10Gi RWX nfs-csi <unset> 5s
# 查看PV
[root@k8s-master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-8494b4c2-2341-4bc8-aeda-8edfa9713139 10Gi RWX Delete Bound default/redis-data nfs-csi <unset> 6s
# 创建一个 Redis 的 Deploymen
[root@k8s-master01 ~]# vim redis-deploy.yaml
[root@k8s-master01 ~]# cat redis-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: redis
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
volumes:
- name: nfs
persistentVolumeClaim:
claimName: redis-data
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/redis:7.2.5
name: redis
ports:
- name: tcp-6379
containerPort: 6379
protocol: TCP
volumeMounts:
- name: nfs
mountPath: /data
livenessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 30
timeoutSeconds: 3
periodSeconds: 30
successThreshold: 1
failureThreshold: 2
readinessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 30
timeoutSeconds: 3
periodSeconds: 30
successThreshold: 1
failureThreshold: 2
imagePullPolicy: IfNotPresent
restartPolicy: Always
dnsPolicy: ClusterFirst
# 启动这个pod
[root@k8s-master01 ~]# kubectl create -f redis-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
redis-5c5bdd7995-5vxvf 1/1 Running 0 96s
# 登陆容器写入测试数据
[root@k8s-master01 ~]# kubectl exec -it redis-5c5bdd7995-5vxvf -- bash
root@redis-5c5bdd7995-5vxvf:/data# redis-cli
127.0.0.1:6379> set a 123
OK
127.0.0.1:6379> get a
"123"
127.0.0.1:6379> set b 456
OK
127.0.0.1:6379> get b
"456"
127.0.0.1:6379> save
OK
127.0.0.1:6379> exit
# 查看后端数据是否有数据写入
[root@harbor ~]# ls /data/nfs/pvc-8494b4c2-2341-4bc8-aeda-8edfa9713139/
dump.rdb
# 新增加一个pod副本
[root@k8s-master01 ~]# kubectl scale deploy redis --replicas=2
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
redis-5c5bdd7995-5vxvf 1/1 Running 0 2m10s
redis-5c5bdd7995-g6tzc 1/1 Running 0 43s
# 登陆新容器写入/修改测试数据
[root@k8s-master01 ~]# kubectl exec -it redis-5c5bdd7995-g6tzc -- bash
root@redis-5c5bdd7995-g6tzc:/data# redis-cli
127.0.0.1:6379> get a # 之前的数据还在
"123"
127.0.0.1:6379> get b
"456"
127.0.0.1:6379>
127.0.0.1:6379> set b 789 # 修改一个key的值
OK
127.0.0.1:6379> get b
"789"
127.0.0.1:6379> save
OK
127.0.0.1:6379> exit
# 回到第一个容器
[root@k8s-master01 ~]# kubectl exec -it redis-5c5bdd7995-5vxvf -- bash
root@redis-5c5bdd7995-5vxvf:/data# redis-cli
127.0.0.1:6379> get b # 发现数据没有被同步
"456"
127.0.0.1:6379>
# 删除pod重新创建
[root@k8s-master01 ~]# kubectl delete -f redis-deploy.yaml
[root@k8s-master01 ~]# kubectl create -f redis-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
redis-5c5bdd7995-sz2t9 1/1 Running 0 38s
# 登陆容器查看数据,不过是最新写入的
[root@k8s-master01 ~]# kubectl exec -it redis-5c5bdd7995-sz2t9 -- bash
root@redis-5c5bdd7995-sz2t9:/data# redis-cli
127.0.0.1:6379> get a
"123"
127.0.0.1:6379> get b
"789"
127.0.0.1:6379> exit
2.8.9 StatefulSet volumeClaimTemplates
使用 StatefulSet 部署有状态服务时,可以使用 volumeClaimTemplates 自动为每个 Pod 生成 PVC,并挂载至容器中,大大降低了手动创建管理存储的难度和复杂度。
假设需要搭建一个三节点的 RabbitMQ 集群到 K8s 中,并且需要实现数据的持久化,此时可以通过 StatefulSet 创建三个副本,并且通过 volumeClaimTemplates 绑定存储。
# 首先下载部署文件
[root@k8s-master01 ~]# git clone https://gitee.com/dukuan/k8s.git
# 更改存储配置
[root@k8s-master01 ~]# cd k8s/k8s-rabbitmq-cluster/
[root@k8s-master01 k8s-rabbitmq-cluster]# vim rabbitmq-cluster-ss.yaml
[root@k8s-master01 k8s-rabbitmq-cluster]# sed -n "86,92p;104,113p" rabbitmq-cluster-ss.yaml
volumeMounts:
- mountPath: /etc/rabbitmq
name: config-volume
readOnly: false
- mountPath: /var/lib/rabbitmq
name: rabbitmq-storage
readOnly: false
volumeClaimTemplates:
- metadata:
name: rabbitmq-storage
spec:
accessModes:
- ReadWriteMany
storageClassName: "nfs-csi"
resources:
requests:
storage: 4Gi
# 创建命名空间
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl create ns public-service
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get ns public-service
NAME STATUS AGE
public-service Active 12s
# 创建资源
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl create -f .
# 查看启动状态(依次创建)
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get po -n public-service
NAME READY STATUS RESTARTS AGE
rmq-cluster-0 1/1 Running 0 6m8s
rmq-cluster-1 1/1 Running 0 3m27s
rmq-cluster-2 1/1 Running 0 102s
查看创建的 PVC:
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get pvc -n public-service
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
rabbitmq-storage-rmq-cluster-0 Bound pvc-1f19eacd-bd71-4503-bb7a-179a904d7e9a 4Gi RWX nfs-csi <unset> 6m41s
rabbitmq-storage-rmq-cluster-1 Bound pvc-3583f5a6-47a0-420f-85b0-13cdf19b8fd8 4Gi RWX nfs-csi <unset> 3m57s
rabbitmq-storage-rmq-cluster-2 Bound pvc-f8cc5eac-575c-4f66-b22e-d40d7d20462c 4Gi RWX nfs-csi <unset> 2m12s
# 查看创建的 Service:
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get svc -n public-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
rmq-cluster ClusterIP None <none> 5672/TCP 7m38s
rmq-cluster-balancer NodePort 10.108.102.139 <none> 15672:31087/TCP,5672:30433/TCP 7m37s
通过 15672 端口的 NodePort 即可访问 RabbitMQ 的管理页面

2.8.10 Pod和PVC处于Pending的原因
- PVC一直Pending的原因
- PVC的空间申请大小大于PV的大小
- PVC的StorageClassName没有和PV的一致
- PVC的accessModes和PV的不一致
- 请求的PV已被其他的PVC绑定
- 挂载的PVC的Pod一直Pending的原因
- PVC没有创建成功/PVC不存在
- PVC和Pod不住同一个Namespace
3、K8s任务管理:Job和CronJob实践
3.1 Job
3.1.1 什么是Job
K8s的Job资源,主要用来执行单次任务,比如执行一次备份任务、微调一次模型等。对于每次任务,Job控制器会创建一个或多个Pod执行相关指令,并且确保成功的个数达到预期的值才会把Job标记为成功(Completed),否则将会标记为失败(Failed)
3.1.2 Job的特点与优势
- 环境隔离:Job可以使用不同的镜像执行任务,无需考虑版本冲突
- 单次执行:Job任务通常是短暂的,运行一次后就结束
- 状态追踪:Job控制器会追踪所有Pod的完成情况,一旦达到指定的成功次数,Job才会被标记为完成
- 失败重试:任务执行失败,可以按需重新执行
- 并行执行:同一个任务可以拆分不同的Pod并行执行,提高执行速度
3.1.3 Job创建
# 使用 Kubectl 创建一个一次性任务,打印一个 Hello
[root@k8s-master01 ~]# kubectl create job hello --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest -- echo "Hello, Job"
# 查看创建的 Job
[root@k8s-master01 ~]# kubectl get job
NAME STATUS COMPLETIONS DURATION AGE
hello Complete 1/1 17s 47s
# DURATION:表示 Job 从开始执行到最后一个 Pod 完成的时间长度
# COMPLETIONS:表示 Job 当前已完成的个数与期望完成次数
# 查看 Job 创建的 Pod
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-4t4hs 0/1 Completed 0 55s
# 查看执行日志
[root@k8s-master01 ~]# kubectl logs -f hello-4t4hs
Hello, Job
3.1.4 Job 并发执行
# 创建模板文件
[root@k8s-master01 ~]# kubectl create job hello-2 --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest --dry-run=client -oyaml -- echo "Hello, Job" > hello-2.yaml
# 修改配置
[root@k8s-master01 ~]# vim hello-2.yaml
[root@k8s-master01 ~]# cat hello-2.yaml
apiVersion: batch/v1
kind: Job
metadata:
creationTimestamp: null
name: hello-2
spec:
parallelism: 2 # 创建 2 个 Pod 同时执行任务
completions: 5 # 有 5 个完成表示任务结束
template:
metadata:
creationTimestamp: null
spec:
containers:
- command:
- sh
- -c
- echo "Hello, Job!"
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest
name: hello-2
resources: {}
restartPolicy: Never
status: {}
# parallelism:并行执行任务的数量。如果数值大于未完成任务的数量,只会创建未完成的数量
# completions:有多少Pod执行成功,认为任务是成功的
# 创建该 Job(一次性创建2个,一共创建5个)
[root@k8s-master01 ~]# kubectl create -f hello-2.yaml
# 查看创建的 Pod
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-tl88t 0/1 ContainerCreating 0 12s
hello-2-xqfjl 0/1 ContainerCreating 0 12s
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-msswf 0/1 ContainerCreating 0 5s
hello-2-tl88t 0/1 Completed 0 59s
hello-2-xffsz 0/1 ContainerCreating 0 4s
hello-2-xqfjl 0/1 Completed 0 59s
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-msswf 0/1 Completed 0 18s
hello-2-tl88t 0/1 Completed 0 72s
hello-2-x86w4 0/1 ContainerCreating 0 4s
hello-2-xffsz 0/1 Completed 0 17s
hello-2-xqfjl 0/1 Completed 0 72s
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-msswf 0/1 Completed 0 22s
hello-2-tl88t 0/1 Completed 0 76s
hello-2-x86w4 0/1 Completed 0 8s
hello-2-xffsz 0/1 Completed 0 21s
hello-2-xqfjl 0/1 Completed 0 76s
3.1.5 Job 重试机制
如果要实现 Pod 执行失败后可以重试,此时可以把重启策略改成 OnFailure,但是最好限制一下重试次数
# 比如最多允许每个 Pod 尝试两次任务执行
[root@k8s-master01 ~]# vim hello-3.yaml
[root@k8s-master01 ~]# cat hello-3.yaml
apiVersion: batch/v1
kind: Job
metadata:
creationTimestamp: null
name: hello-3
spec:
parallelism: 2 # 创建 2 个 Pod 同时执行任务
completions: 5 # 有 5 个完成表示任务结束
backoffLimit: 2 # 每个 Pod 最大重启次数
template:
metadata:
creationTimestamp: null
spec:
containers:
- command:
- sh
- -c
- echo "Hello, Job!" && sleep 1 && exit 42
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest
name: hello-3
resources: {}
restartPolicy: OnFailure
status: {}
# backoffLimit:如果任务执行失败,失败多少次后不再执行
# ttlSecondAfterFinished:Job在执行结束之后(状态为completed或者Failed)自动清理。设置为0表示执行结束立即删除,不设置则不会清除
# 创建该 Job
[root@k8s-master01 ~]# kubectl create -f hello-3.yaml
# 查看创建的 Pod(此时 Pod 会重启且最多重启两次)
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-3-4tr7m 0/1 ContainerCreating 0 6s
hello-3-gwpc8 0/1 ContainerCreating 0 6s
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-3-4tr7m 0/1 CrashLoopBackOff 1 (50s ago) 2m2s
hello-3-gwpc8 0/1 ErrImagePull 0 2m2s
# 重启结束后,Job 会被标记为失败
[root@k8s-master01 ~]# kubectl get job
NAME STATUS COMPLETIONS DURATION AGE
hello-3 Failed 0/5 5m14s 5m14s
3.2 CronJob
3.2.1 什么是CronJob
K8s是CronJob资源,是Job资源的延伸,主要用于周期性执行任务,比如定期备份数据库、定期对某个服务健康检查等。CronJob和Linux等计划任务功能类似,主要用于按计划执行某个操作,但功能比Linux远胜等计划任务更加丰富。
3.2.2 CronJob的特点与优势
- 周期执行:CronJob使用cron表达式定期创建Job执行任务
- 并行执行:CronJob具备多种并发策略,调用Job更加灵活
- 可靠性高:CronJob可以调度K8s的任意节点执行,防止单点故障
- 环境隔离:CronJob可以使用不同的镜像执行任务,无需考虑版本冲突
- 历史记录:CronJob可以保留多个成功和失败的任务,便于问题追踪和审计
3.2.3 CronJob 创建
# 创建一个每分钟执行一次的任务
[root@k8s-master01 ~]# kubectl create cj hello --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest --schedule='*/1 * * * *' -- echo "Hello, Hello from the Kubernetes CronJob"
# 查看创建的 CronJob
[root@k8s-master01 ~]# kubectl get cj
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * <none> False 0 <none> 10s
# SUSPEND:是否暂停调度
# ACTIVE:当前处于活跃的 Job 个数
# LAST SCHEDULE:上一次成功调度的时间
# TIMEZONE:CronJob 执行调度时所使用的时区,默认为 UTC
# 等待 1 分钟后,即可查看到调度的 Job
[root@k8s-master01 ~]# kubectl get job
NAME STATUS COMPLETIONS DURATION AGE
hello-29161974 Complete 1/1 6s 7s
3.2.4 CronJob 并发策略
CronJob 支持三种并发策略:
- Allow:允许同时运行多个任务,默认值
- Forbid:不允许并发运行,如果之前的任务尚未完成,新的任务不会被创建
- Replace:如果之前的任务尚未完成,新的任务会替换的之前的任务
如需更改并发策略,只需要更改 CronJob 的 concurrencyPolicy 字段即可。比如不允许 CronJob 并发执行:
# 修改配置
[root@k8s-master01 ~]# vim hello-2.yaml
[root@k8s-master01 ~]# cat hello-2.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: hello-2
spec:
schedule: '*/1 * * * *'
concurrencyPolicy: Forbid # 不允许并发运行,如果之前的任务尚未完成,新的任务不会被创建
jobTemplate:
metadata:
creationTimestamp: null
name: hello-2
spec:
template:
metadata:
creationTimestamp: null
spec:
containers:
- command:
- sh
- -c
- echo Hello, Hello from the Kubernetes CronJob; sleep 70
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest
name: hello-2
resources: {}
restartPolicy: OnFailure
# 创建该计划任务:
[root@k8s-master01 ~]# kubectl create -f hello-2.yaml
[root@k8s-master01 ~]# kubectl get cj
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
hello-2 */1 * * * * <none> False 0 <none> 4s
# 该任务在第一个创建 Job 后,第二个任务不会在70s后创建:
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-29162006-ztnhd 0/1 Completed 0 75s
hello-2-29162007-hcfpq 0/1 ContainerCreating 0 0s
# 第一次调度的 Pod 并未结束,同时由于并发策略的控制,下一次在 Pod 完成时不会调度
# 如果并发策略改成替换,下一次任务将覆盖上一次任务(避免长时间无法结束的任务)
[root@k8s-master01 ~]# vim hello-2.yaml
[root@k8s-master01 ~]# cat hello-2.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: hello-2
spec:
schedule: '*/1 * * * *'
concurrencyPolicy: Replace # 如果之前的任务尚未完成,新的任务会替换的之前的任务
....
# 重新创建该计划任务:
[root@k8s-master01 ~]# kubectl create -f hello-2.yaml
[root@k8s-master01 ~]# kubectl get cj
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
hello-2 */1 * * * * <none> False 0 <none> 7s
# 一分钟后,Pod 为结束,下一次的调度会覆盖上一次的任务
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-29162004-c7tqx 1/1 Terminating 0 60s
hello-2-29162005-w6kxp 0/1 ContainerCreating 0 0s
3.2.5 CronJob 执行记录
CronJob 默认的执行记录保留方式如下:
- 成功记录:默认为 3 次,可以通过 successfulJobsHistoryLimit 字段更改
- 失败记录:默认为 1 次,可以通过 failedJobsHistoryLimit 字段更改
为了更好的收集日志和追踪问题,可以增加记录的数量。比如都增加到 5 个
[root@k8s-master01 ~]# cat hello-2.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: hello-2
spec:
schedule: '*/1 * * * *'
failedJobsHistoryLimit: 5
successfulJobsHistoryLimit: 5
....
3.2.6 CronJob 调度时区
如果采用具体的时间调度任务,需要注意调度的时区问题。
如果 CronJob 未标注调度时区,Kubernetes 会以 kube-controller-manager 组件的时区进行调度,如果该组件运行的时区和本地时区不一样,会导致无法按照规定时间进行调度。
比如创建一个每天凌晨一点开始执行的任务,此时配置的调度表达式可能如下:
schedule: '00 01 * * *'
但是如果未指定时区,当 kube-controller-manager 组件的时区和本地时区有差别时,可能会在每天的九点进行调度(本地时区为 Shanghai 时区,kube-controller-manager 采用 UTC 时间)
在 Kubernetes 1.25 版本时,CronJob 增加了.spec.timeZone 的字段用于配置 CronJob 的调
度时区,在 1.27 版本达到稳定,可以直接使用。1.25~1.27 版本之间需要打开 kube-apiserver 的featuregates 特性,比如:--feature-gates=CronJobTimeZone=true
配置时区只需要添加 timeZone 即可:
[root@k8s-master01 ~]# cat hello-2.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: hello-2
spec:
schedule: '*/1 * * * *'
timeZone: "Asia/Shanghai"
...
3.3 任务管理实战
3.3.1 使用 CronJob 定期备份 MySQL
# 创建一个测试的 MySQL
[root@k8s-master01 ~]# kubectl create deploy mysql --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/mysql:8.0.20 --dry-run=client -oyaml > mysql-deploy.yaml
[root@k8s-master01 ~]# vim mysql-deploy.yaml
[root@k8s-master01 ~]# cat mysql-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: mysql
name: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/mysql:8.0.20
name: mysql
ports:
- name: tcp-3306
containerPort: 3306
protocol: TCP
env:
- name: MYSQL_ROOT_PASSWORD
value: password_123
# 创建
[root@k8s-master01 ~]# kubectl create -f mysql-deploy.yaml
deployment.apps/mysql created
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
mysql-86c9b6bb4c-wsrc2 1/1 Running 0 3s
# 创建一个 MySQL 的 Service
[root@k8s-master01 ~]# kubectl expose deploy mysql --port 3306
service/mysql exposed
[root@k8s-master01 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
....
mysql ClusterIP 10.106.69.170 <none> 3306/TCP 7s
# 创建一个测试库和表
[root@k8s-master01 ~]# kubectl exec -it mysql-86c9b6bb4c-wsrc2 -- bash
root@mysql-86c9b6bb4c-wsrc2:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.20 MySQL Community Server - GPL
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> create database test;
Query OK, 1 row affected (0.04 sec)
mysql> use test;
Database changed
mysql> CREATE TABLE employees (id INT AUTO_INCREMENT PRIMARY KEY,first_name VARCHAR(50) NOT NULL,last_name VARCHAR(50) NOT NULL,email VARCHAR(100),hire_date DATE);
Query OK, 0 rows affected (0.02 sec)
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| employees |
+----------------+
1 row in set (0.00 sec)
mysql>
[root@k8s-master01 csi-driver-nfs]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-csi nfs.csi.k8s.io Delete Immediate false 12h
# 创建备份的持久化 PVC
[root@k8s-master01 ~]# vim mysql-pv.yaml
[root@k8s-master01 ~]# cat mysql-pv.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-backup-data
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-csi
[root@k8s-master01 ~]# kubectl create -f mysql-pv.yaml
[root@k8s-master01 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
mysql-backup-data Bound pvc-daa50aa2-a773-4cef-874a-1551d77b2b29 10Gi RWX nfs-csi <unset> 4s
[root@k8s-master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-daa50aa2-a773-4cef-874a-1551d77b2b29 10Gi RWX Delete Bound default/mysql-backup-data nfs-csi <unset> 5s
# 创建 CronJob 每天凌晨 1 点执行备份
[root@k8s-master01 ~]# vim mysql-backup.yaml
[root@k8s-master01 ~]# cat mysql-backup.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: mysql-backup
spec:
schedule: '0 1 * * *'
timeZone: "Asia/Shanghai"
failedJobsHistoryLimit: 5
successfulJobsHistoryLimit: 5
jobTemplate:
spec:
template:
metadata:
creationTimestamp: null
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: mysql-backup-data
restartPolicy: Never
containers:
- command:
- sh
- -c
- |
mysqldump -hmysql.default -P3306 -uroot -p'password_123' --all-databases > /mnt/all-`date +%Y%m%d-%H%M%S`.sql;
ls /mnt
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/mysql:8.0.20
name: mysql-backup
volumeMounts:
- name: data
mountPath: /mnt
# 创建cronJob
[root@k8s-master01 ~]# kubectl create -f mysql-backup.yaml
[root@k8s-master01 ~]# kubectl get cj
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
mysql-backup 0 1 * * * <none> False 0 <none> 3s
# 模拟启动测试
# 启动一个叫mysql-backup-test的job,指定来源与我们建好的cronjob
[root@k8s-master01 ~]# kubectl create job mysql-backup-test --from=cronjob/mysql-backup
# 查看job
[root@k8s-master01 ~]# kubectl get job
NAME STATUS COMPLETIONS DURATION AGE
mysql-backup-test Complete 1/1 6s 28s
# 查看pod
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-backup-test-9tln7 0/1 Completed 0 30s
# 查看日志是否备份
[root@k8s-master01 ~]# kubectl logs -f mysql-backup-test-9tln7
mysqldump: [Warning] Using a password on the command line interface can be insecure.
3.3.2 使用 CronJob 定期重启 K8s 服务
有时候需要定期重启 K8s 中的服务,也可以使用 CronJob 实现
# 创建一个用于放置 CronJob 的命名空间
[root@k8s-master01 ~]# kubectl create ns cronjob
# 创建重启 K8s 服务的权限
# 创建 ClusterRole
[root@k8s-master01 ~]# kubectl create clusterrole deployment-restart --verb=get,update,patch --resource=deployments.apps
# 绑定权限
[root@k8s-master01 ~]# kubectl create clusterrolebinding deployment-restart-binding --clusterrole=deployment-restart --serviceaccount=cronjob:default
# 创建 CronJob 执行重启任务(每天凌晨一点执行)
[root@k8s-master01 ~]# vim mysql-restart.yaml
[root@k8s-master01 ~]# cat mysql-restart.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: mysql-restart
spec:
schedule: '0 1 * * *'
timeZone: "Asia/Shanghai"
failedJobsHistoryLimit: 3
successfulJobsHistoryLimit: 3
concurrencyPolicy: Allow
suspend: false
jobTemplate:
spec:
template:
metadata:
creationTimestamp: null
spec:
restartPolicy: Never
containers:
- command:
- /bin/bash
- -c
- >-
kubectl rollout restart deploy mysql -n default
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kubectl:latest
name: mysql-restart
# 创建 CronJob:
[root@k8s-master01 ~]# kubectl create -f mysql-restart.yaml -n cronjob
[root@k8s-master01 ~]# kubectl get cj -n cronjob
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
mysql-restart 0 1 * * * Asia/Shanghai False 0 <none> 21s
# 模拟启动测试
# 启动一个叫mysql-backup-test的job,指定来源与我们建好的cronjob
[root@k8s-master01 ~]# kubectl create job mysql-restart-test --from=cronjob/mysql-restart -n cronjob
# 查看job
[root@k8s-master01 ~]# kubectl get job -n cronjob
NAME STATUS COMPLETIONS DURATION AGE
mysql-restart-test Complete 1/1 6s 20s
# 查看pod及日志
[root@k8s-master01 ~]# kubectl get pod -n cronjob
NAME READY STATUS RESTARTS AGE
mysql-restart-test-jtjdg 0/1 Completed 0 31s
[root@k8s-master01 ~]# kubectl logs -f mysql-restart-test-jtjdg -n cronjob
# 测试pod已经重启
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-5b9499c559-gjlt9 1/1 Running 0 66s
六、K8s管理
- 六、K8s管理
- 1、K8s配置管理:ConfigMap和Secret实践
- 2、K8s存储管理:数据持久化及动态存储
- 3、K8s任务管理:Job和CronJob实践
- 六、K8s管理
- 1、K8s配置管理:ConfigMap和Secret实践
- 2、K8s存储管理:数据持久化及动态存储
1、K8s配置管理:ConfigMap和Secret实践
1.1 ConfigMap介绍及资源定义
ConfigMap也是k8s的一种资源,主要用于存储配置数据,如程序的环境变量、配置文件等。ConfigMap可以实现把应用程序等配置信息从容器镜像或者代码中分离出来,从而可以更容易等管理和更新配置,而不必重新构建应用程序。
主要用途:
- 存储配置信息:剥离应用程序配置,实现配置分离
- 动态配置:动态更高程序的配置,无需重新部署整个应用
- 共享配置:数据共享,多个Pod可以共享ConfigMap数据
1.2 创建 ConfigMap
1.2.1 基于 yaml 文件创建
# 基于 ConfigMap 资源创建:
[root@k8s-master01 ~]# vim basic-cm.yaml
[root@k8s-master01 ~]# cat basic-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: basic-config
data:
key1: test01
key2: test02
[root@k8s-master01 ~]# kubectl create -f basic-cm.yaml
[root@k8s-master01 ~]# kubectl get cm
NAME DATA AGE
basic-config 2 18s
# 查看yaml配置
[root@k8s-master01 ~]# kubectl get cm basic-config -oyaml
apiVersion: v1
data: # key
key1: test01 # value
key2: test02
kind: ConfigMap
metadata:
creationTimestamp: "2025-06-16T14:26:29Z"
name: basic-config
namespace: default
resourceVersion: "9640"
uid: 10e39b92-1154-48b9-b046-877fa687fd19
# 基于多行value 创建:
[root@k8s-master01 ~]# vim cm-multiple.yaml
[root@k8s-master01 ~]# cat cm-multiple.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: multiple-config
data:
multiple.conf: |- # key
port: 6379 # 全是 multiple.conf的value
bind: 0.0.0.0 # 全是 multiple.conf的value
pass: PASSWORD # 全是 multiple.conf的value
[root@k8s-master01 ~]# kubectl create -f cm-multiple.yaml
[root@k8s-master01 ~]# kubectl get cm multiple-config
NAME DATA AGE
multiple-config 1 14s
查看数据:
[root@k8s-master01 ~]# kubectl describe cm multiple-config
Name: multiple-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
multiple.conf:
----
port: 6379
bind: 0.0.0.0
pass: PASSWORD
BinaryData
====
Events: <none>
1.2.2 基于文件创建
1、基于单个文件
# 创建测试文件
[root@k8s-master01 ~]# vim nginx.conf
[root@k8s-master01 ~]# cat nginx.conf
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
# kubectl create cm [cm名称] [--from-file=基于创建的文件]
[root@k8s-master01 ~]# kubectl create cm nginx.conf --from-file=nginx.conf
[root@k8s-master01 ~]# kubectl get cm nginx.conf
NAME DATA AGE
nginx.conf 1 9s
[root@k8s-master01 ~]# kubectl describe cm nginx.conf
Name: nginx.conf
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
nginx.conf:
----
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
BinaryData
====
Events: <none>
2、基于文件创建并更改 key 名:
[root@k8s-master01 ~]# kubectl create cm nginx.conf-02 --from-file=nginx.conf02=nginx.conf
[root@k8s-master01 ~]# kubectl get cm nginx.conf-02
NAME DATA AGE
nginx.conf-02 1 5s
[root@k8s-master01 ~]# kubectl describe cm nginx.conf-02
Name: nginx.conf-02 # cm的名称
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
nginx.conf02: # 自定义的key名称
----
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
BinaryData
====
Events: <none>
3、基于多个文件创建:
# 创建测试文件
[root@k8s-master01 ~]# echo "mysqld" > my.cnf
[root@k8s-master01 ~]# echo "Redis" > redis.conf
[root@k8s-master01 ~]# kubectl create cm multile-file --from-file=my.cnf --from-file=redis.conf
[root@k8s-master01 ~]# kubectl get cm multile-file
NAME DATA AGE
multile-file 2 7s # data数据显示2
# 查看数据
[root@k8s-master01 ~]# kubectl describe cm multile-file
Name: multile-file
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
my.cnf:
----
mysqld
redis.conf:
----
Redis
BinaryData
====
Events: <none>
1.2.3 基于文件夹创建
# 创建测试文件夹
[root@k8s-master01 ~]# mkdir conf
[root@k8s-master01 ~]# mv my.cnf redis.conf ./conf
[root@k8s-master01 ~]# ls ./conf/
my.cnf redis.conf
[root@k8s-master01 ~]# kubectl create cm dirconfig --from-file=./conf
[root@k8s-master01 ~]# kubectl get cm dirconfig
NAME DATA AGE
dirconfig 2 5s
# 查看数据
[root@k8s-master01 ~]# kubectl describe cm dirconfig
Name: dirconfig
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
my.cnf:
----
mysqld
redis.conf:
----
Redis
BinaryData
====
Events: <none>
1.2.4 基于环境变量创建
创建 ConfigMap 时,可以通过--from-env-file 创建环境变量类型的 ConfigMap。创建环境变量类型的 ConfigMap,只需要在文件内写入 KEY=VALUE 格式的内容即可创建:
# 创建环境变量文件
[root@k8s-master01 ~]# vim env-file
[root@k8s-master01 ~]# cat env-file
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASS=password
[root@k8s-master01 ~]# kubectl create cm env-cm --from-env-file=env-file
[root@k8s-master01 ~]# kubectl get cm env-cm
NAME DATA AGE
env-cm 4 6s # 显示4个变量数据
# 一共四个key对
[root@k8s-master01 ~]# kubectl describe cm env-cm
Name: env-cm
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
DB_HOST: # key
----
localhost # value
DB_PASS: # key
----
password # value
DB_PORT: # key
----
3306 # value
DB_USER: # key
----
root # value
BinaryData
====
Events: <none>
1.2.5 基于 literal 创建
# Literal 一般用于创建环境变量形式的 ConfigMap:
[root@k8s-master01 ~]# kubectl create configmap example-config --from-literal=key1=config1 --from-literal=key2=config2
[root@k8s-master01 ~]# kubectl get cm example-config
NAME DATA AGE
example-config 2 10s
[root@k8s-master01 ~]# kubectl describe cm example-config
Name: example-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
key1:
----
config1
key2:
----
config2
BinaryData
====
Events: <none>
1.3 更新 ConfigMap
# 基于 Yaml 文件修改
kubectl replace -f basic-cm.yaml
# 直接通过 edit 修改
kubectl edit cm nginx-conf
# 通过源文件修改
1、首先修改本地文件:
vim nginx.conf
2、先转成 yaml 再进行 replace
kubectl create cm nginx-conf --from-file=nginx.conf=nginx.conf --dry-run=client -oyaml | kubectl replace -f -
1.4 挂载 ConfigMap
1.4.1 以文件形式挂载
# 创建一个模版并修改yaml文件
[root@k8s-master01 ~]# kubectl create deploy nginx --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15 --dry-run=client -oyaml > nginx.cm.yaml
# 修改cm挂载yaml文件
[root@k8s-master01 ~]# vim nginx.cm.yaml
[root@k8s-master01 ~]# cat nginx.cm.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
volumes:
- name: conf
configMap:
name: multile-file
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: conf
mountPath: /mnt
[root@k8s-master01 ~]# kubectl create -f nginx.cm.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-868bcb4db4-8shkf 1/1 Running 0 5s
# 查看挂在详情,默认情况下会以 ConfigMap 中的 Key 名作为挂载的文件名
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-868bcb4db4-8shkf -- bash
root@nginx-cm-868bcb4db4-8shkf:/# ls /mnt/
my.cnf redis.conf
1.4.2 指定挂载的文件名
[root@k8s-master01 ~]# vim nginx.items.yaml
[root@k8s-master01 ~]# vim nginx.items.yaml
[root@k8s-master01 ~]# cat nginx.items.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
volumes:
- name: conf
configMap:
name: multile-file
items: # 挂载 ConfigMap 时,可以指定挂载的文件名字
- key: my.cnf # 源名字
path: mysql.conf # 重新起的名字
optional: true # 如果上述key书写有误就可导致pod一直处于ContainerCreating状态。可以添加 optional 字段,把 key 必须存在改为非必须
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: conf
mountPath: /mnt
[root@k8s-master01 ~]# kubectl create -f nginx.items.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-85c4759ff7-tp4gm 1/1 Running 0 7s
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-85c4759ff7-tp4gm -- bash
# 发现就只剩下一个key。这是items一个局限性,只会挂载 items 配置的 key。如果不想key缺失,就必须在items下面把俩key全写上
root@nginx-cm-85c4759ff7-tp4gm:/# ls /mnt/
mysql.conf
1.4.3 自定义挂载权限
[root@k8s-master01 ~]# vim nginx.chmod.yaml
[root@k8s-master01 ~]# cat nginx.chmod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
volumes:
- name: conf
configMap:
name: multile-file
items:
- key: my.cnf
path: mysql.conf
- key: redis.conf
path: redis.conf-2
mode: 0777 # 局部权限,对单个 item 配置权限,八进制
defaultMode: 420 # 默认权限,对所有的 item 生效,十进制
# 权限支持八进制和十进制,任意选择即可
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: conf
mountPath: /mnt
[root@k8s-master01 ~]# kubectl create -f nginx.chmod.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-86dd9b4cd5-qjvbl 1/1 Running 0 3s
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-86dd9b4cd5-qjvbl -- bash
# 实际上挂载的文件是被软连接过去的
root@nginx-cm-86dd9b4cd5-qjvbl:/# ls -l /mnt/
total 0
lrwxrwxrwx 1 root root 17 Jun 11 11:56 mysql.conf -> ..data/mysql.conf
lrwxrwxrwx 1 root root 19 Jun 11 11:56 redis.conf-2 -> ..data/redis.conf-2
# 我们要查看它的源文件
root@nginx-cm-86dd9b4cd5-qjvbl:/# ls -l /mnt/..data/
total 8
-rw-r--r-- 1 root root 7 Jun 11 11:56 mysql.conf # 全局权限644已生效
-rwxrwxrwx 1 root root 6 Jun 11 11:56 redis.conf-2 # 局部权限777已生效。如果同时设置了局部权限和全局权限,局部权限要高于全局权限
1.4.4 文件挂载覆盖的问题
[root@k8s-master01 ~]# vim nginx.mount.yaml
[root@k8s-master01 ~]# cat nginx.mount.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
volumes:
- name: conf
configMap:
name: nginx.conf
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: conf
mountPath: /etc/nginx # 挂载目录
[root@k8s-master01 ~]# kubectl create -f nginx.mount.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-8449fbc4bd-mjbwn 0/1 CrashLoopBackOff 3 (<invalid> ago) 79s
# ConfigMap 和 Secret 挂载时,会直接覆盖原目录的内容,所以在挂载时需要注意。
# 比如需要挂载 nginx.conf 时,如果直接挂载至/etc/nginx 目录,会导致 nginx 无法启动:
[root@k8s-master01 ~]# kubectl logs -f nginx-cm-8449fbc4bd-mjbwn
2025/06/11 12:51:36 [emerg] 1#1: open() "/etc/nginx/mime.types" failed (2: No such file or directory) in /etc/nginx/nginx.conf:14
nginx: [emerg] open() "/etc/nginx/mime.types" failed (2: No such file or directory) in /etc/nginx/nginx.conf:14
[root@k8s-master01 ~]# cat nginx.mount.yaml
....
name: nginx
volumeMounts:
- name: conf
mountPath: /etc/nginx/nginx.conf # 指定挂载具体的文件路径
subPath: nginx.conf # 指定挂载具体的文件
[root@k8s-master01 ~]# kubectl create -f nginx.mount.yaml
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-cm-5486dcb56d-6ndxg 1/1 Running 0 3s
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-5486dcb56d-6ndxg -- bash
root@nginx-cm-5486dcb56d-6ndxg:/# ls /etc/nginx/
conf.d koi-utf mime.types nginx.conf uwsgi_params
fastcgi_params koi-win modules scgi_params win-utf
1.4.5 以环境变量形式挂载
[root@k8s-master01 ~]# kubectl get cm env-cm -oyaml
apiVersion: v1
data:
DB_HOST: localhost
DB_PASS: password
DB_PORT: "3306"
DB_USER: root
....
1、挂载指定的环境变量:
[root@k8s-master01 ~]# vim nginx.mv.yaml
[root@k8s-master01 ~]# cat nginx.mv.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
env: # 支持多组
- name: MYSQL_HOST # 变量的名称
valueFrom:
configMapKeyRef:
name: env-cm # 从哪个 ConfigMap 取的变量
key: DB_HOST # ConfigMap里的key
- name: MYSQL_PASS
valueFrom:
configMapKeyRef:
name: env-cm
key: DB_PASS
# 注意:Key 不存在同样会导致 Pod 一直处于 CreateContainerConfigError 状态
[root@k8s-master01 ~]# kubectl create -f nginx.mv.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-fb6fb686-j8jxz 1/1 Running 0 3s
# 进入容器查看变量已经生效
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-fb6fb686-j8jxz -- bash
root@nginx-cm-fb6fb686-j8jxz:/# env|grep MYSQL
MYSQL_HOST=localhost
MYSQL_PASS=password
2、批量生成环境变量:
[root@k8s-master01 ~]# vim nginx.mv.yaml
[root@k8s-master01 ~]# cat nginx.mv.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-cm
name: nginx-cm
spec:
replicas: 1
selector:
matchLabels:
app: nginx-cm
template:
metadata:
labels:
app: nginx-cm
spec:
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
envFrom:
- configMapRef:
name: env-cm # 从哪个 ConfigMap 取的变量
prefix: CM_ # 批量生成不支持修改变量名,但是支持增加一个前缀
[root@k8s-master01 ~]# kubectl create -f nginx.mv.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-cm-5d75d59887-f52h4 1/1 Running 0 2s
# 进入容器查看变量已经生效,且增加了一个前缀
[root@k8s-master01 ~]# kubectl exec -it nginx-cm-5d75d59887-f52h4 -- bash
root@nginx-cm-5d75d59887-f52h4:/# env | grep CM_
CM_DB_USER=root
CM_DB_HOST=localhost
CM_DB_PORT=3306
CM_DB_PASS=password
1.5 Secret介绍及资源定义
和ConfigMap类似,Secret也是k8s的一种资源,同样可以存储配置数据。和ConfigMap不同的是Secret提供了一种相对安全的方式来管理这些数据。同时相当于ConfigMap,Secret也有不同的类型,不同的类型具有不同的使用场景。比如Docker Registry Secret可以用了管理镜像仓库的用户名密码。
主要用途:
- 存储敏感信息:可以存储密码、Key等信息
- 数据保护:Secret的数据可以被加密存储,防止未经授权的访问
- 动态配置:动态更高程序的配置,无需重新部署整个应用
- 共享配置:数据共享,多个Pod可以共享ConfigMap数据
Secret常见类型
- Opaque:通用型Secret默认类型
- kubernetes.io/dockerconfigjson:下载私有仓库镜像使用的Secret,和宿主机的/root/.docker/config.json 一致,宿主机登陆后即可产生该文件
- kubernetes.io/tls:用于存储HTTPS域名证书文件的Secret,可以被Ingress使用
- 其他不常用类型:bootstrap.kubernetes.io/token、kubernetes.io/basic-auth、kubernetes.io/ssh-auth、kubernetes.io/service-account-token
1.6 创建 Secret
1.6.1 使用 literal 创建
# 可以使用 literal 创建,创建后会自动加密
# kubectl create secret [类型] [名称] --from-literal=[加密内容]
[root@k8s-master01 ~]# kubectl create secret generic basic-auth-secret --from-literal=username=admin --from-literal=password=securepassword
secret/basic-auth-secret created
# 查看创建的 Secret 资源:
[root@k8s-master01 ~]# kubectl get secret
NAME TYPE DATA AGE
basic-auth-secret Opaque 2 18s
[root@k8s-master01 ~]#
[root@k8s-master01 ~]# kubectl get secret basic-auth-secret -oyaml
apiVersion: v1
data:
password: c2VjdXJlcGFzc3dvcmQ= # 加密后的数据
username: YWRtaW4= # 加密后的数据
kind: Secret
metadata:
creationTimestamp: "2025-06-16T16:35:34Z"
name: basic-auth-secret
namespace: default
resourceVersion: "21360"
uid: 8e3a2a40-e234-4863-8b63-e7e65d5ed7a9
type: Opaque
# 解密
[root@k8s-master01 ~]# echo "c2VjdXJlcGFzc3dvcmQ=" | base64 -d
securepassword
[root@k8s-master01 ~]# echo "YWRtaW4=" | base64 -d
admin
1.6.2 基于 Yaml 文件创建
基于 yaml 文件形式创建,有两种方式:
1、第一种直接写在 data 下,需要对数据进行先加密:
# 要对数据进行先加密
[root@k8s-master01 ~]# echo "dar3adada" | base64
ZGFyM2FkYWRhCg==
[root@k8s-master01 ~]# vim secret.data.yaml
[root@k8s-master01 ~]# cat secret.data.yaml
apiVersion: v1
data:
ssh-key: ZGFyM2FkYWRhCg== # 加密后的数据
kind: Secret
metadata:
name: secret1
namespace: default
type: Opaque
[root@k8s-master01 ~]# kubectl create -f secret.data.yaml
[root@k8s-master01 ~]# kubectl get secret secret1
NAME TYPE DATA AGE
secret1 Opaque 1 9s
# 查看
[root@k8s-master01 ~]# kubectl get secret secret1 -oyaml
apiVersion: v1
data:
ssh-key: ZGFyM2FkYWRhCg==
kind: Secret
metadata:
creationTimestamp: "2025-06-16T16:43:57Z"
name: secret1
namespace: default
resourceVersion: "22114"
uid: 2ae95461-321c-49c6-b1cb-c47a70c6ecf1
type: Opaque
2、第二种是写在 stringData 下,无需进行先加密:
[root@k8s-master01 ~]# vim secret.stringdata.yaml
[root@k8s-master01 ~]# cat secret.stringdata.yaml
apiVersion: v1
stringData:
ssh-key: password # 直接写在 stringData 下,无需进行先加密
kind: Secret
metadata:
name: secret2
namespace: default
type: Opaque
[root@k8s-master01 ~]# kubectl create -f secret.stringdata.yaml
[root@k8s-master01 ~]# kubectl get secret secret2
NAME TYPE DATA AGE
secret2 Opaque 1 14s
[root@k8s-master01 ~]# kubectl get secret secret2 -oyaml
apiVersion: v1
data:
ssh-key: cGFzc3dvcmQ= # 数据已经加密
kind: Secret
metadata:
creationTimestamp: "2025-06-16T16:48:29Z"
name: secret2
namespace: default
resourceVersion: "22523"
uid: 073b81ac-ee11-41e5-8878-d1576774cc31
type: Opaque
1.6.3 基于文件和目录创建
1、基于文件创建
# env-file文件内容
[root@k8s-master01 ~]# cat env-file
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASS=password
# 基于文件创建和 ConfigMap 类似:
[root@k8s-master01 ~]# kubectl create secret generic env --from-file=env-file
[root@k8s-master01 ~]# kubectl get secret env
NAME TYPE DATA AGE
env Opaque 1 10s
[root@k8s-master01 ~]# kubectl get secret env -oyaml
apiVersion: v1
data:
env-file: REJfSE9TVD1sb2NhbGhvc3QKREJfUE9SVD0zMzA2CkRCX1VTRVI9cm9vdApEQl9QQVNTPXBhc3N3b3JkCg== # 数据已经被加密
kind: Secret
metadata:
creationTimestamp: "2025-06-16T16:51:57Z"
name: env
namespace: default
resourceVersion: "22834"
uid: ccbc63b5-2dc3-4e6a-a664-f67d41a51786
type: Opaque
2、基于目录创建
# 查看目录下的文件
[root@k8s-master01 ~]# ls conf/
my.cnf redis.conf
# 基于目录创建和 ConfigMap 类似:
[root@k8s-master01 ~]# kubectl create secret generic conf --from-file=./conf
[root@k8s-master01 ~]# kubectl get secret conf
NAME TYPE DATA AGE
conf Opaque 2 7s
[root@k8s-master01 ~]# kubectl get secret conf -oyaml
apiVersion: v1
data:
my.cnf: bXlzcWxkCg== # 数据已被加密
redis.conf: UmVkaXMK # 数据已被加密
kind: Secret
metadata:
creationTimestamp: "2025-06-16T16:54:32Z"
name: conf
namespace: default
resourceVersion: "23067"
uid: 7f344d9c-88bf-4913-825b-5b019623f55a
type: Opaque
1.6.4 使用 Secret 管理镜像仓库密钥
# 如果需要拉取私有仓库的镜像,需要给 deploy 等资源配置镜像仓库的密钥,此时可以先创建镜像仓库的密钥:
[root@k8s-master01 ~]# kubectl create secret docker-registry myregistrykey --docker-server=v1.0 --docker-username=ywb --docker-password=dot --docker-email=173@99.com
[root@k8s-master01 ~]# kubectl get secret myregistrykey
NAME TYPE DATA AGE
myregistrykey kubernetes.io/dockerconfigjson 1 12s
[root@k8s-master01 ~]# kubectl get secret myregistrykey -oyaml
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJ2MS4wIjp7InVzZXJuYW1lIjoieXdiIiwicGFzc3dvcmQiOiJkb3QiLCJlbWFpbCI6IjE3M0A5OS5jb20iLCJhdXRoIjoiZVhkaU9tUnZkQT09In19fQ==
kind: Secret
metadata:
creationTimestamp: "2025-06-16T17:00:46Z"
name: myregistrykey
namespace: default
resourceVersion: "23630"
uid: 7ad602c9-8191-40df-85b5-c11ce579a0fa
type: kubernetes.io/dockerconfigjson
# 解析下数据,看下格式
[root@k8s-master01 ~]# echo "eyJhdXRocyI6eyJ2MS4wIjp7InVzZXJuYW1lIjoieXdiIiwicGFzc3dvcmQiOiJkb3QiLCJlbWFpbCI6IjE3M0A5OS5jb20iLCJhdXRoIjoiZVhkaU9tUnZkQT09In19fQ==" | base64 -d
{"auths":{"v1.0":{"username":"ywb","password":"dot","email":"173@99.com","auth":"eXdiOmRvdA=="}}}
- docker-registry:指定 Secret 的类型
- myregistrykey: Secret 名称
- DOCKER_REGISTRY_SERVER:镜像仓库地址
- OCKER_USER:镜像仓库用户名,需要有拉取镜像的权限
- DOCKER_PASSWORD:镜像仓库密码
- OCKER_EMAIL:邮箱信息,可以为空
之后在 Pod 字段下添加该 Secret 即可:
spec:
imagePullSecrets:
- name: myregistrykey
containers:
1.6.5 使用 Secret 管理域名证书
Secret 可以使用 kubernetes.io/tls 类型管理域名的证书,然后用于 Ingress:
# 生成测试证书
[root@k8s-master01 ~]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=test.com"
# 创建 Secret
[root@k8s-master01 ~]# kubectl -n default create secret tls nginx-test-tls --key=tls.key --cert=tls.crt
[root@k8s-master01 ~]# kubectl get secret nginx-test-tls
NAME TYPE DATA AGE
nginx-test-tls kubernetes.io/tls 2 79s
# 之后即可在 Ingress 中使用:
tls:
- secretName: nginx-test-tls
1.7 挂载 Secret
1.7.1 以文件形式挂载
[root@k8s-master01 ~]# vim nginx.se.yaml
[root@k8s-master01 ~]# cat nginx.se.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-se
name: nginx-se
spec:
replicas: 1
selector:
matchLabels:
app: nginx-se
template:
metadata:
labels:
app: nginx-se
spec:
volumes:
- name: conf
secret: # 和 ConfigMap 类似,修改类型
secretName: basic-auth-secret # 名字跟ConfigMap不一致
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: conf
mountPath: /mnt
[root@k8s-master01 ~]# kubectl create -f nginx.se.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-se-6d8c867-bnb9p 1/1 Running 0 13s
# 进入容器查看(挂载后,文件内容会被解密)
[root@k8s-master01 ~]# kubectl exec -it nginx-se-6d8c867-bnb9p -- bash
root@nginx-se-6d8c867-bnb9p:/# cat /mnt/username
admin
root@nginx-se-6d8c867-bnb9p:/# cat /mnt/password
securepassword
1.7.2 以环境变量形式挂载
Secret 以环境变量形式挂载和 ConfigMap 类似,只是一些字段上的差异:
[root@k8s-master01 ~]# vim nginx.mount.yaml
[root@k8s-master01 ~]# cat nginx.mount.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-se
name: nginx-se
spec:
replicas: 1
selector:
matchLabels:
app: nginx-se
template:
metadata:
labels:
app: nginx-se
spec:
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
env: # 挂载指定的环境变量
- name: USERNAME
valueFrom:
secretKeyRef:
name: basic-auth-secret # secret名称
key: username # secret的key
envFrom: # 批量生成环境变量
- prefix: SECRET_ # 生成前缀,可选
secretRef:
name: basic-auth-secret # secret名称
[root@k8s-master01 ~]# kubectl create -f nginx.mount.yaml
deployment.apps/nginx-se created
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-se-7cb9899b7d-75nmf 1/1 Running 0 2s
[root@k8s-master01 ~]#
[root@k8s-master01 ~]# kubectl exec -it nginx-se-7cb9899b7d-75nmf --bash
error: unknown flag: --bash
See 'kubectl exec --help' for usage.
[root@k8s-master01 ~]# kubectl exec -it nginx-se-7cb9899b7d-75nmf -- bash
root@nginx-se-7cb9899b7d-75nmf:/# env | grep USERNAME
USERNAME=admin
root@nginx-se-7cb9899b7d-75nmf:/# env | grep SECRET_
SECRET_password=securepassword
SECRET_username=admin
1.7.3 使用 Secret 拉取私有仓库的镜像
假设有个镜像在私有仓库中,未使用账号密码是无法拉取镜像的,此时可以在部署资源中,添加 docker-registry 类型的 Secret。
假设创建一个 deployment,镜像所在的位置是私有仓库,如果未配置密钥,则会有如下报
错:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 35s default-scheduler Successfully assigned default/nginx-se-587f485dd6-6nshf to k8s-node01
Normal Pulling 34s (x2 over 46s) kubelet Pulling image "192.168.200.53/taobao/nginx:1.15"
Warning Failed 34s (x2 over 46s) kubelet Failed to pull image "192.168.200.53/taobao/nginx:1.15": failed to pull and unpack image "192.168.200.53/taobao/nginx:1.15": failed to resolve reference "192.168.200.53/taobao/nginx:1.15": pull access denied, repository does not exist or may require authorization: authorization failed: no basic auth credentials
Warning Failed 34s (x2 over 46s) kubelet Error: ErrImagePull
Normal BackOff 21s (x3 over 46s) kubelet Back-off pulling image "192.168.200.53/taobao/nginx:1.15"
Warning Failed 21s (x3 over 46s) kubelet Error: ImagePullBackOff
# 添加 Secret:
[root@k8s-master01 ~]# kubectl create secret docker-registry harbor --docker-server=192.168.200.53 --docker-username=taobao --docker-password=Taobao12345 --docker-email=taobao@qq.com
secret/harbor created
[root@k8s-master01 ~]# kubectl get secret harbor
NAME TYPE DATA AGE
harbor kubernetes.io/dockerconfigjson 1 9s
[root@k8s-master01 ~]# vim nginx.ha.yaml
[root@k8s-master01 ~]# cat nginx.ha.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-se
name: nginx-se
spec:
replicas: 1
selector:
matchLabels:
app: nginx-se
template:
metadata:
labels:
app: nginx-se
spec:
containers:
- image: 192.168.200.53/taobao/nginx:1.15
name: nginx
imagePullSecrets:
- name: harbor # 登录密匙
[root@k8s-master01 ~]# kubectl create -f nginx.ha.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-se-664b6bd89b-vpqtf 1/1 Running 0 7s
1.8 ConfigMap 和 Secret 注意事项
- 需提前创建 ConfigMap 和 Secret
- ConfigMap 和 Secret 必须要和 Pod 或者是引用它资源在同一个命名空间
- 如果引用了某个 key,需要确保引用的 Key 必须存在
- envFrom、valueFrom 无法热更新环境变量,subPath 也是无法热更新的
- envFrom 配置环境变量,如果 key 是无效的,它会忽略掉无效的 key
- ConfigMap 和 Secret 最好不要太大
- ConfigMap 和 Secret 支持热更新,但是程序未必支持!
1.9 ConfigMap 和 Secret区别
两者都可以管理程序的配置文件,只是Secret采用base64加密value数据,并且Secret提供了更多的类型以支持不同的使用场景
2、K8s存储管理:数据持久化及动态存储
2.1 常见的存储需求
- 用户数据
- 共享数据
- 文件数据
- 程序数据
- 配置文件
- 日志文件
2.2 什么是 Volumes
k8s是Volumes是一个对存储资源的抽象,属于pod级别的一个配置字段。
Volumes在pod中绑定多个、多种的数据类型,比如NFS、NAS、CEPH等,这些绑定的数据,可以挂载到pod中的一个或多个容器中,从而实现容器的数据持久化和数据共享。

2.3 Volumes常用类型
- EmptyDir:临时目录,当pod从节点删除时,EmptyDir中的数据也会被删除,常用于临时数据存储,如缓存、中间计算结果、数据共享等
- HostPath:节点数据共享,HostPath可以让容器直接访问节点上的文件或者目录,常用于和节点共享数据
- ConfigMap&Secret:用于挂载ConfigMap和Secret到容器中
- Downward API:元数据挂载,主要用于容器访问pod的一些元数据,比如标签、命名空间等
- NFS&NAS:网络文件系统,主要挂载远程存储到容器中,实现跨主机的数据共享和持久化
- PVC:pv请求,k8s中的一类资源,用于配置多种不同的存储后端
2.4 EmptyDir
EmptyDir 是 Kubernetes 支持的临时存储功能,主要用于多个容器的数据共享,当 Pod 重建时,数据会被清空。EmptyDir 可以绑定主机上的硬盘和内存作为 Volume,比如把 emptyDir.medium字段设置为 Memory,就可以让 Kubernetes 使用 tmpfs(内存支持的文件系统)。虽然 tmpfs 非常快,但是设置的大小会被计入到 Container 的内存限制当中。
2.4.1 使用 Volumes 直接绑定存储
[root@k8s-master01 ~]# vim emptydir-deploy.yaml
[root@k8s-master01 ~]# cat emptydir-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: emptydir
name: emptydir
spec:
replicas: 2
selector:
matchLabels:
app: emptydir
template:
metadata:
labels:
app: emptydir
spec:
volumes:
- name: share-volume # 名字
emptyDir: {} # 挂载类型({}:磁盘挂载)
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: share-volume
mountPath: /opt # 挂载到容器的目录
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/redis:7.2.5
name: redis
volumeMounts:
- name: share-volume
mountPath: /mnt # 挂载到容器的目录
[root@k8s-master01 ~]# kubectl create -f emptydir-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-58b5997b8b-2hf8s 2/2 Running 0 4s
emptydir-58b5997b8b-rsjmx 2/2 Running 0 4s
# 进入到第一个pod的nginx容器中
[root@k8s-master01 ~]# kubectl exec -it emptydir-58b5997b8b-2hf8s -c nginx -- bash
# 往共享目录写入一个数据文件
root@emptydir-58b5997b8b-2hf8s:/# echo "123" > /opt/test
# 进入到第一个pod的redis容器中
[root@k8s-master01 ~]# kubectl exec -it emptydir-58b5997b8b-2hf8s -c redis -- bash
# 数据文件成功被共享
root@emptydir-58b5997b8b-2hf8s:/data# cat /mnt/test
123
# 进到容器中即可查看到挂载的目录:
root@emptydir-58b5997b8b-2hf8s:/data# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
/dev/mapper/rl_k8s--node02-root xfs 49G 5.9G 44G 12% /mnt
....
# 只会在同一个pod里面的容器就行共享
# 进入到第二个pod的redis容器中
[root@k8s-master01 ~]# kubectl exec -it emptydir-58b5997b8b-rsjmx -c redis -- bash
root@emptydir-58b5997b8b-rsjmx:/data# ls /mnt/
root@emptydir-58b5997b8b-rsjmx:/data#
2.4.2 使用内存类型 EmptyDir
[root@k8s-master01 ~]# vim emptydir-deploy.yaml
[root@k8s-master01 ~]# cat emptydir-deploy.yaml
....
spec:
volumes:
- name: share-volume
emptyDir:
medium: Memory # 使用内存作为 EmptyDir,只需要把 medium 改为 Memory 即可
containers:
....
# 重新加载配置
[root@k8s-master01 ~]# kubectl replace -f emptydir-deploy.yaml
# pod已经更新
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-54647cc6df-ctfgm 2/2 Running 0 4s
emptydir-54647cc6df-fcfms 2/2 Running 0 6s
# 进入容器
[root@k8s-master01 ~]# kubectl exec -it emptydir-54647cc6df-ctfgm -c nginx -- bash
# 查看挂载的内存目录
root@emptydir-54647cc6df-ctfgm:/# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
tmpfs tmpfs 3.5G 0 3.5G 0% /opt
....
2.4.3 EmptyDir 大小限制
两种类型的 EmptyDir 都支持限制卷的大小,只需要添加 sizeLimit 字段即可:
1、内存类型的 emptyDir 不会超出限制的大小,如不限制将会使用机器内存的最大值,或容器内存限制之和的最大值
[root@k8s-master01 ~]# vim emptydir-deploy.yaml
[root@k8s-master01 ~]# cat emptydir-deploy.yaml
....
spec:
volumes:
- name: share-volume
emptyDir:
medium: Memory
sizeLimit: 10Mi # 限制卷的大小
containers:
....
[root@k8s-master01 ~]# kubectl replace -f emptydir-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-78578cbc5f-8lfrk 2/2 Running 0 9s
emptydir-78578cbc5f-kqktp 2/2 Running 0 6s
[root@k8s-master01 ~]# kubectl exec -it emptydir-78578cbc5f-8lfrk -c nginx -- bash
# 挂载大小与配置的一致
root@emptydir-78578cbc5f-8lfrk:/# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
tmpfs tmpfs 10M 0 10M 0% /opt
...
# 模拟制造20M文件,显示空间不足,说明针对内存限制生效了
root@emptydir-78578cbc5f-8lfrk:/# cd /opt/
root@emptydir-78578cbc5f-8lfrk:/opt# dd if=/dev/zero of=./file bs=1M count=20
dd: error writing './file': No space left on device
11+0 records in
10+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0.00880329 s, 1.2 GB/s
root@emptydir-78578cbc5f-8lfrk:/opt# du -sh /opt/
10M /opt/
2、磁盘类型的 emptyDir,限制大小后不会显示具体限制的大小
2. 磁盘类型的超出最大限制时,Pod 将会变成 Completed 状态,同时将会创建一个Pod
[root@k8s-master01 ~]# vim emptydir-deploy.yaml
[root@k8s-master01 ~]# cat emptydir-deploy.yaml
....
spec:
volumes:
- name: share-volume
emptyDir:
sizeLimit: 10Mi # 对磁盘挂载限制10M
containers:
....
[root@k8s-master01 ~]# kubectl replace -f emptydir-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-75b4898bf8-bwflb 2/2 Running 0 5s
emptydir-75b4898bf8-pcjjr 2/2 Running 0 7s
[root@k8s-master01 ~]# kubectl exec -it emptydir-75b4898bf8-bwflb -c nginx -- bash
root@emptydir-75b4898bf8-bwflb:/# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
/dev/mapper/rl_k8s--node02-root xfs 49G 5.9G 44G 12% /opt
....
# 模拟制造20M文件,但是并没有报错
root@emptydir-75b4898bf8-bwflb:/# cd /opt/
root@emptydir-75b4898bf8-bwflb:/opt# dd if=/dev/zero of=./file bs=1M count=20
20+0 records in
20+0 records out
20971520 bytes (21 MB, 20 MiB) copied, 0.0296455 s, 707 MB/s
root@emptydir-75b4898bf8-bwflb:/opt# du -sh /opt/
20M /opt/
# 退出容器等待2分钟查看pod状态
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-75b4898bf8-9w5t7 2/2 Running 0 3s
emptydir-75b4898bf8-bwflb 0/2 ContainerStatusUnknown 1 2m17s
emptydir-75b4898bf8-pcjjr 2/2 Running 0 2m19s
2.5 hostPath
HostPath 类型的卷可将节点上的文件或目录挂载到 Pod 上,用于容器和节点之间的数据共享。
比如使用 hostPath 类型的卷,将主机的/data 目录挂载到 Pod 的/test-pd 目录:
[root@k8s-master01 ~]# vim emptydir-deploy.yaml
[root@k8s-master01 ~]# cat emptydir-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: emptydir
name: emptydir
spec:
replicas: 2
selector:
matchLabels:
app: emptydir
template:
metadata:
labels:
app: emptydir
spec:
volumes:
- name: data
hostPath: # 挂载类型
path: /data # 宿主机挂载路径,不存在自动创建
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: data
mountPath: /opt # 容器挂载路径
[root@k8s-master01 ~]# kubectl replace -f emptydir-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
emptydir-5b5f58b7b5-n9bcc 1/1 Running 0 8s
emptydir-5b5f58b7b5-vfqlr 1/1 Running 0 6s
# 进入容器创建一个测试文件
[root@k8s-master01 ~]# kubectl exec -it emptydir-5b5f58b7b5-n9bcc -- bash
root@emptydir-5b5f58b7b5-n9bcc:/# echo "123" > /opt/test
# 退出容器发现宿主机并没有自动创建挂载文件
root@emptydir-5b5f58b7b5-n9bcc:/# exit
exit
[root@k8s-master01 ~]# ll /data
ls: cannot access '/data': No such file or directory
# 原来只会在pod所在节点创建,查看pod在哪个节点上面(k8s-node01)
[root@k8s-master01 ~]# kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
emptydir-5b5f58b7b5-n9bcc 1/1 Running 0 2m55s 192.168.85.215 k8s-node01 <none> <none>
emptydir-5b5f58b7b5-vfqlr 1/1 Running 0 2m53s 192.168.58.233 k8s-node02 <none> <none>
# 在k8s-node01节点发现自动创建了文件
[root@k8s-node01 ~]# ll /data/
total 4
-rw-r--r-- 1 root root 4 Jun 12 02:19 test
# 而且刚才在容器内测试的文件也同步了过来;同时再添加一个测试文件
[root@k8s-node01 ~]# cat /data/test
123
[root@k8s-node01 ~]# echo "gogo" >> /data/node
# 重新登陆容器查看,数据已经同步
[root@k8s-master01 ~]# kubectl exec -it emptydir-5b5f58b7b5-n9bcc -- bash
root@emptydir-5b5f58b7b5-n9bcc:/# ls /opt/
node test
hostPath 卷常用的 type(类型)如下:
- type 为空字符串:默认选项,意味着挂载 hostPath 卷之前不会执行任何检查。
- DirectoryOrCreate:如果给定的 path 不存在任何东西,那么将根据需要创建一个权限为 0755 的空目录,和 Kubelet 具有相同的组和权限。
- Directory:目录必须存在于给定的路径下。
- FileOrCreate:如果给定的路径不存储任何内容,则会根据需要创建一个空文件,权限设置为 0644,和 Kubelet 具有相同的组和所有权。
- File:文件,必须存在于给定路径中。
- Socket:UNIX 套接字,如某个程序的 socket 文件,必须存在于给定路径中。
- CharDevice:字符设备,如串行端口、声卡、摄像头等,必须存在于给定路径中,且只有 Linux 支持。
- BlockDevice:块设备,如硬盘等,必须存在于给定路径中,且只有 Linux 支持。
2.6 NFS/NAS
使用远程存储介质,可以实现跨主机容器之间的数据共享,比如使用 NFS、NAS、CEPH 等。
2.6.1 NFS 搭建
# 准备一台机器用于搭建 NFS(为了节省资源,之前拿之前的harbor机器来测试)
# 首先服务端安装 NFS:
[root@harbor ~]# yum install nfs-utils rpcbind -y
# 配置共享目录:
[root@harbor ~]# mkdir -p /data/nfs
# 允许哪个网段可以挂载共享目录,有什么权限
[root@harbor ~]# echo "/data/nfs/ 192.168.200.0/24(rw,sync,no_subtree_check,no_root_squash)" >> /etc/exports
# 加载 NFS 配置
[root@harbor ~]# exportfs -r
# 启动NFS
[root@harbor ~]# systemctl enable --now nfs-server rpcbind
# 客户端同样安装NFS(咱们这里所有k8s节点都装一下)
[root@k8s-master01 ~]# yum install nfs-utils rpcbind -y
# 启动NFS
[root@k8s-master01 ~]# systemctl enable --now nfs-server rpcbind
# 挂载测试
[root@k8s-master01 ~]# mount -t nfs 192.168.200.53:/data/nfs /mnt/
[root@k8s-master01 ~]# df -hT | grep nfs
192.168.200.53:/data/nfs nfs4 99G 6.0G 93G 7% /mnt
[root@k8s-master01 ~]# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
192.168.200.53:/data/nfs nfs4 99G 6.0G 93G 7% /mnt
2.6.2 挂载 NFS 类型的卷
[root@k8s-master01 ~]# vim nfs-deploy.yaml
[root@k8s-master01 ~]# cat nfs-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nfs
name: nfs
spec:
replicas: 2
selector:
matchLabels:
app: nfs
template:
metadata:
labels:
app: nfs
spec:
volumes:
- name: nfs-volume
nfs:
server: 192.168.200.53 # 挂载NFS的IP
path: /data/nfs # 挂载NFS的目录
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: nfs-volume
mountPath: /mnt # 挂载在容器的目录
[root@k8s-master01 ~]# kubectl create -f nfs-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nfs-56d768cd6f-6xpsc 1/1 Running 0 4s
nfs-56d768cd6f-gpx54 1/1 Running 0 4s
# 进入容器并添加一个测试文件
[root@k8s-master01 ~]# kubectl exec -it nfs-56d768cd6f-6xpsc -- bash
root@nfs-56d768cd6f-6xpsc:/# ls /mnt/
root@nfs-56d768cd6f-6xpsc:/# echo "123" > /mnt/test
# 到NFS服务器上面检查数据是否同步
[root@harbor ~]# ll /data/nfs/
total 4
-rw-r--r-- 1 root root 4 Jun 13 20:33 test
[root@harbor ~]# cat /data/nfs/test
123
# 添加一个测试文件
[root@harbor ~]# echo "ceshi" > /data/nfs/test02
# 容器内也已经同步
root@nfs-56d768cd6f-6xpsc:/# ls /mnt/
test test02
2.7 使用 PV 和 PVC 挂载存储
2.7.1 PV和PVC基本概念
PersistentVolume:简称PV,是由K8是管理员设置到存储,可以配置Ceph、NFS、GlusterFS等常用存储,相对于Volume配置,提供了更多等功能,比如生命周期等管理、大小的限制,同时也可以支持存储的动态分配。
PersistentVolumeClaim:简称PVC,是对存储PV的请求,表示需要什么类型的PV,并绑定该PV。如果某个服务需要挂载存储,只需要在Volumes中添加PVC类型的Volume即可,并且通常只需要指定PVC的名称即可,无需关心整个的存储配置细节。
2.7.2 PV和PVC存储管理架构

2.7.3 PV 访问策略
- ReadWriteOnce:可以被单节点以读写模式挂载,命令行中可以被缩写为 RWO
- ReadOnlyMany:可以被多个节点以只读模式挂载,命令行中可以被缩写为 ROX
- ReadWriteMany:可以被多个节点以读写模式挂载,命令行中可以被缩写为 RWX
- ReadWriteOncePod:只能被单个 Pod 以读写模式挂载,命令行中可以被缩写为 RWOP
常见存储支持的访问策略:
| Volume Plugin | ReadWriteOnce | ReadOnlyMany | ReadWriteMany | ReadWriteOncePod |
|---|---|---|---|---|
| AzureFile | ✓ | ✓ | ✓ | - |
| CephFS | ✓ | ✓ | ✓ | - |
| CSI | 取决于存储本身 | 取决于存储本身 | 取决于存储本身 | 取决于存储本身 |
| FC | ✓ | ✓ | - | - |
| FlexVolume | ✓ | ✓ | 取决于存储本身 | - |
| HostPath | ✓ | - | - | - |
| iSCSI | ✓ | ✓ | - | - |
| NFS | ✓ | ✓ | ✓ | - |
| RBD | ✓ | ✓ | - | - |
| VsphereVolume | ✓ | - | -(works whenPods arecollocated) | - |
| PortworxVolume | ✓ | - | ✓ | - |
2.7.4 PV 回收策略
- Retain:保留,该策略允许手动回收资源,当删除 PVC 时,PV 仍然存在,PV 被视为已释放,管理员可以手动回收卷。
- Recycle:回收,如果 Volume 插件支持,Recycle 策略会对卷执行 rm -rf 清理该 PV,并使其可用于下一个新的 PVC,但是本策略将来会被弃用,目前只有 NFS 和 HostPath 支持该策略。
- Delete:删除,如果 Volume 插件支持,删除 PVC 时会同时删除 PV,动态存储默认为Delete,目前支持 Delete 的存储后端包括 AWS EBS、GCE PD、Azure Disk、OpenStackCinder、Ceph 等。
如果更改回收策略,只需要通过 persistentVolumeReclaimPolicy 字段配置即可
2.7.5 HostPath 类型的 PV
# 创建 HostPath 类型的 PV
[root@k8s-master01 ~]# vim hostpath-pv.yaml
[root@k8s-master01 ~]# cat hostpath-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
labels:
type: local
name: hostpath-pv
spec:
storageClassName: hostpath # PV的类,一个特定类型的PV 只能绑定到特定类别的PVC
capacity: # 容量配置
storage: 10Gi
accessModes: # 该 PV 的访问模式
- ReadWriteOnce # 可以被单节点以读写模式挂载,命令行中可以被缩写为RWO
hostPath:
path: "/mnt"
# 创建该 PV
[root@k8s-master01 ~]# kubectl create -f hostpath-pv.yaml
persistentvolume/hostpath-pv created
# 查看 PV 的状态
[root@k8s-master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
hostpath-pv 10Gi RWO Retain Available hostpath <unset>
# pv的状态:
# Available:可用,没有被 PVC 绑定的空闲资源。
# Bound:已绑定,已经被 PVC 绑定。
# Released:已释放,PVC 被删除,但是资源还未被重新使用。
# Failed:失败,自动回收失败
2.7.6 NFS/NAS 类型的 PV
# 创建一个 NAS/NFS 类型的 PV
[root@k8s-master01 ~]# vim nfs-pv.yaml
[root@k8s-master01 ~]# cat nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs
spec:
capacity: # 容量配置
storage: 5Gi
volumeMode: Filesystem # 卷的模式,目前支持 Filesystem(文件系统) 和 Block(块),其中 Block类型需要后端存储支持,默认为文件系统
accessModes: # 该 PV 的访问模式
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle # 回收策略
storageClassName: nfs-slow # PV的类,一个特定类型的PV只能绑定到特定类别的 PVC
nfs:
server: 192.168.200.53 # 挂载NFS的IP
path: /data/nfs # 挂载NFS的目录
# 创建该pv
[root@k8s-master01 ~]# kubectl create -f nfs-pv.yaml
# 查看 PV 的状态
[root@k8s-master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
....
pv-nfs 5Gi RWX Recycle Available nfs-slow <unset> 34s
2.7.7 创建 PVC 绑定 PV
# 创建一个 PVC 绑定到 NFS 的 PV 上
[root@k8s-master01 ~]# vim pvc-nfs.yaml
[root@k8s-master01 ~]# cat pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: task-pvc-claim # PVC的名称
spec:
storageClassName: nfs-slow # 想要从哪个pv绑定
accessModes: # 该 PV 的访问模式
- ReadWriteMany # 可以被多个节点以读写模式挂载,命令行中可以被缩写为RWX
resources:
requests:
storage: 3Gi # 能挂载的磁盘大小不能超过nfs-slow这个pv
# 创建该pvc
[root@k8s-master01 ~]# kubectl create -f pvc-nfs.yaml
persistentvolumeclaim/task-pvc-claim created
# 查看 PVC 的状态
[root@k8s-master01 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
task-pvc-claim Bound pv-nfs 5Gi RWX nfs-slow <unset> 3s
# 通过 PVC 把存储挂载到容器中
[root@k8s-master01 ~]# vim nginx-pvc.yaml
[root@k8s-master01 ~]# cat nginx-pvc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-pvc
name: nginx-pvc
spec:
replicas: 2
selector:
matchLabels:
app: nginx-pvc
template:
metadata:
labels:
app: nginx-pvc
spec:
volumes:
- name: data
persistentVolumeClaim: # PVC
claimName: task-pvc-claim # 绑定哪个PVC
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
volumeMounts:
- name: data
mountPath: /mnt
[root@k8s-master01 ~]# kubectl create -f nginx-pvc.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-pvc-9fc455d4f-nggqw 1/1 Running 0 19s
nginx-pvc-9fc455d4f-qp4xn 1/1 Running 0 19s
# 进入一个容器查看挂载详情
[root@k8s-master01 ~]# kubectl exec -it nginx-pvc-9fc455d4f-nggqw -- bash
root@nginx-pvc-9fc455d4f-nggqw:/# df -hT
Filesystem Type Size Used Avail Use% Mounted on
....
192.168.200.53:/data/nfs nfs4 99G 6.2G 93G 7% /mnt
....
# 并输入一个测试文件
root@nginx-pvc-9fc455d4f-nggqw:/# echo "123" > /mnt/test
# 从NFS节点查看共享目录
[root@habor ~]# ll /data/nfs/
total 4
-rw-r--r-- 1 root root 4 Jun 18 22:17 test
# 同时添加一个测试文件
[root@habor ~]# echo "gogo" > /data/nfs/ceshi
# 回到容器查看共享目录已经更新
root@nginx-pvc-9fc455d4f-nggqw:/# ls /mnt/
ceshi test
# 查看另外一个pod的容器内的共享目录也已经更新
[root@k8s-master01 ~]# kubectl exec -it nginx-pvc-9fc455d4f-qp4xn -- bash
root@nginx-pvc-9fc455d4f-qp4xn:/# ls /mnt/
ceshi test
2.7.8 PVC 创建和挂载失败的原因
- PVC 一直 Pending 的原因:
- PVC 的空间申请大小大于 PV 的大小
- PVC 的 StorageClassName 没有和 PV 的一致
- PVC 的 accessModes 和 PV 的不一致
- 请求的 PV 已被其他的 PVC 绑定
- 挂载 PVC 的 Pod 一直处于 Pending:
- PVC 没有创建成功/PVC 不存在
- PVC 和 Pod 不在同一个 Namespace
2.8 动态存储
2.8.1 什么是动态存储
动态存储可以在用户需要存储资源时自动创建和配置PV,而无需手动创建和配置PV。可以让存储资源的分配变得更加灵活,而且可以随着应用程序的需求变化而动态调整
2.8.2 动态存储工作原理
动态存储依赖 StorageClass 和 CSI(Container Storage Interface) 实现,当我们创建一个PVC时,通过 StorageClassName 指定动态存储的类,该类指向了不同的存储供应商,比如Ceph、NFS等,之后通过该类就可完成PV的自动创建。
2.8.3 什么时CSI和StorageClass
1、容器存储接口-CSI
CSI:(Container Storage InterFace)是一个标准化的存储接口,用于在容器环境中集成外部存储系统。它提供里一种统一的方式来集成各种存储系统,无论是云提供商的存储服务,还是自建的存储集群,都可以通过CSI对接到容器平台中。
在同一个K8s集群中,可以同时存在多个CSI对接不同到存储平台,之后可以通过StorageClass的provisioner字段声明该Class对接哪一种存储平台
2、存储类-StorageClass
StorageClass和IngressClass类似,用于定义存储资源的类别,可以使用内置的存储插件或者第三方的CSI驱动程序关联存储。
在同一个K8s集群中,可以同时存在多个StorageClass对接不同或相同的CSI,用于实现更多的动态存储需求场景。
2.8.4 安装 NFS/NAS CS
# 下载安装文件
[root@k8s-master01 ~]# git clone https://gitee.com/dukuan/csi-driver-nfs.git
# 安装 CSI
[root@k8s-master01 ~]# cd csi-driver-nfs/
[root@k8s-master01 csi-driver-nfs]# sed -i "s#registry.k8s.io#k8s.m.daocloud.io#g" deploy/v4.9.0/*.yaml
[root@k8s-master01 csi-driver-nfs]# ./deploy/install-driver.sh v4.9.0 local
# 查看 Pod 状态
[root@k8s-master01 ~]# kubectl get po -n kube-system -l app=csi-nfs-node
NAME READY STATUS RESTARTS AGE
csi-nfs-node-hf6b5 3/3 Running 2 (7m20s ago) 9m59s
csi-nfs-node-n7xl5 3/3 Running 1 (4m25s ago) 9m59s
csi-nfs-node-r6vv2 3/3 Running 1 9m59s
[root@k8s-master01 ~]# kubectl get po -n kube-system -l app=csi-nfs-controller
NAME READY STATUS RESTARTS AGE
csi-nfs-controller-5c9687ccf5-2j7s8 4/4 Running 2 (4m58s ago) 10m
# 查看 CSI
[root@k8s-master01 ~]# kubectl get csidriver
NAME ATTACHREQUIRED PODINFOONMOUNT STORAGECAPACITY TOKENREQUESTS REQUIRESREPUBLISH MODES AGE
nfs.csi.k8s.io false false false <unset> false Persistent 3m41s
2.8.5 创建 StorageClass
# 更改 StorageClass 配置:
[root@k8s-master01 csi-driver-nfs]# vim deploy/v4.9.0/storageclass.yaml
[root@k8s-master01 csi-driver-nfs]# cat deploy/v4.9.0/storageclass.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
server: 192.168.200.53 # 更改为NFS的IP地址
share: /data/nfs # NFS的挂载目录
# csi.storage.k8s.io/provisioner-secret is only needed for providing mountOptions in DeleteVolume
# csi.storage.k8s.io/provisioner-secret-name: "mount-options"
# csi.storage.k8s.io/provisioner-secret-namespace: "default"
reclaimPolicy: Delete
volumeBindingMode: Immediate
mountOptions:
- nfsvers=4.1
# 创建sc
[root@k8s-master01 csi-driver-nfs]# kubectl create -f deploy/v4.9.0/storageclass.yaml
# 查看sc
[root@k8s-master01 csi-driver-nfs]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-csi nfs.csi.k8s.io Delete Immediate false 6s
2.8.6 挂载测试
# 创建 PVC
[root@k8s-master01 csi-driver-nfs]# cat deploy/example/pvc-nfs-csi-dynamic.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs-dynamic
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-csi
[root@k8s-master01 csi-driver-nfs]# kubectl create -f deploy/example/pvc-nfs-csi-dynamic.yaml
# 查看 PVC 和 PV:
# 直接就是绑定的状态,对应的pv是一个随机的串
[root@k8s-master01 csi-driver-nfs]# kubectl get pvc pvc-nfs-dynamic
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
pvc-nfs-dynamic Bound pvc-7f79caa6-31d6-430f-8b42-15e04f7f52f3 10Gi RWX nfs-csi <unset> 8s
[root@k8s-master01 csi-driver-nfs]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
....
pvc-7f79caa6-31d6-430f-8b42-15e04f7f52f3 10Gi RWX Delete Bound default/pvc-nfs-dynamic nfs-csi <unset> 13s
# 此时会在 NFS 目录下创建一个共享目录
[root@habor ~]# ls /data/nfs/
pvc-7f79caa6-31d6-430f-8b42-15e04f7f52f3
[root@k8s-master01 csi-driver-nfs]# vim nfs-deploy.yaml
[root@k8s-master01 csi-driver-nfs]# cat nfs-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nfs-pvc
name: nfs-pvc
spec:
replicas: 1
selector:
matchLabels:
app: nfs-pvc
template:
metadata:
labels:
app: nfs-pvc
spec:
volumes:
- name: nfs
persistentVolumeClaim:
claimName: pvc-nfs-dynamic # 挂载的PVC
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
name: nginx
command:
- "/bin/bash"
- "-c"
- while true; do echo $(hostname) $(date) >> /mnt/outfile;sleep 5; done
volumeMounts:
- name: nfs
mountPath: /mnt
readOnly: false
# 启动
[root@k8s-master01 csi-driver-nfs]# kubectl create -f nfs-deploy.yaml
[root@k8s-master01 csi-driver-nfs]# kubectl get po
NAME READY STATUS RESTARTS AGE
nfs-pvc-64f7c5d487-4zgdd 1/1 Running 0 6s
nfs-pvc-64f7c5d487-97rlg 1/1 Running 0 6s
nfs-pvc-64f7c5d487-vvdww 1/1 Running 0 6s
# 在 NFS 节点的数据目录,即可查看到来自于不同示例写入的数据
[root@harbor ~]# tail -10f /data/pvc-7f79caa6-31d6-430f-8b42-15e04f7f52f3/outfile
nfs-pvc-64f7c5d487-6vbf7 Thu Jun 12 05:56:22 UTC 2025
nfs-pvc-64f7c5d487-6vbf7 Thu Jun 12 05:56:27 UTC 2025
nfs-pvc-64f7c5d487-97rlg Thu Jun 12 06:00:33 UTC 2025
nfs-pvc-64f7c5d487-vvdww Thu Jun 12 06:00:33 UTC 2025
nfs-pvc-64f7c5d487-4zgdd Thu Jun 12 05:59:57 UTC 2025
nfs-pvc-64f7c5d487-97rlg Thu Jun 12 06:00:38 UTC 2025
nfs-pvc-64f7c5d487-vvdww Thu Jun 12 06:00:38 UTC 2025
nfs-pvc-64f7c5d487-4zgdd Thu Jun 12 06:00:02 UTC 2025
nfs-pvc-64f7c5d487-97rlg Thu Jun 12 06:00:43 UTC 2025
....
# 删除pod,删除pvc
[root@k8s-master01 csi-driver-nfs]# kubectl delete deploy nfs-pvc
[root@k8s-master01 csi-driver-nfs]# kubectl delete pvc pvc-nfs-dynamic
# pv也会被自动删除
[root@k8s-master01 csi-driver-nfs]# kubectl get pv
No resources found
# 共享目录里的数据也会被删除
[root@harbor ~]# ll /data/nfs/
total 0
2.8.7 部署 MySQL 并持久化数据
[root@k8s-master01 ~]# vim mysql-pvc.yaml
[root@k8s-master01 ~]# cat mysql-pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-csi
# 创建一个 MySQL 的 PVC
[root@k8s-master01 ~]# kubectl create -f mysql-pvc.yaml
# 查看PVC
[root@k8s-master01 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
mysql-data Bound pvc-5f0e69a1-177c-4bcd-a61a-3ba8a49830c7 10Gi RWX nfs-csi <unset> 4s
# 查看PV
[root@k8s-master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-5f0e69a1-177c-4bcd-a61a-3ba8a49830c7 10Gi RWX Delete Bound default/mysql-data nfs-csi <unset> 8s
# 创建一个 MYSQL 的 Deployment
[root@k8s-master01 ~]# vim mysql-deploy.yaml
[root@k8s-master01 ~]# cat mysql-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: mysql
name: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
volumes:
- name: nfs
persistentVolumeClaim:
claimName: mysql-data
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/mysql:8.0.20
name: mysql
ports:
- name: tcp-3306
containerPort: 3306
protocol: TCP
env:
- name: MYSQL_ROOT_PASSWORD
value: password_123
volumeMounts:
- name: nfs
mountPath: /var/lib/mysql
livenessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 30
timeoutSeconds: 3
periodSeconds: 30
successThreshold: 1
failureThreshold: 2
readinessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 30
timeoutSeconds: 3
periodSeconds: 30
successThreshold: 1
failureThreshold: 2
imagePullPolicy: IfNotPresent
restartPolicy: Always
dnsPolicy: ClusterFirst
# 启动这个pod
[root@k8s-master01 ~]# kubectl create -f mysql-deploy.yaml
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-6b4dcc8748-bqpx7 1/1 Running 0 2m9s
# 有状态的服务不允许多个实例去访问同一个数据的
# 新起一个pod
[root@k8s-master01 ~]# kubectl scale deploy mysql --replicas=2
# 一直在报错
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-6b4dcc8748-bqpx7 1/1 Running 0 5m22s
mysql-6b4dcc8748-ll7sd 0/1 Running 0 90s
[root@k8s-master01 ~]# kubectl logs -f mysql-6b4dcc8748-ll7sd
....
2025-06-12T06:33:45.744663Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2025-06-12T06:33:47.197596Z 1 [ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
2025-06-12T06:33:48.211322Z 1 [ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
2025-06-12T06:33:49.232938Z 1 [ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
....
# 进入容器
[root@k8s-master01 ~]# kubectl exec -it mysql-6b4dcc8748-bqpx7 -- bash
# 登陆mysql创建一个库
root@mysql-6b4dcc8748-bqpx7:/# mysql -uroot
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
root@mysql-6b4dcc8748-bqpx7:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 32
Server version: 8.0.20 MySQL Community Server - GPL
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.05 sec)
mysql> create database test;
Query OK, 1 row affected (0.02 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
5 rows in set (0.01 sec)
# MySQL 启动后,会在数据目录初始化基础数据,此时可以在后端存储中看到
[root@harbor ~]# ls /data/nfs/pvc-5f0e69a1-177c-4bcd-a61a-3ba8a49830c7/
auto.cnf binlog.index client-cert.pem '#ib_16384_1.dblwr' ib_logfile0 '#innodb_temp' performance_schema server-cert.pem test
binlog.000001 ca-key.pem client-key.pem ib_buffer_pool ib_logfile1 mysql private_key.pem server-key.pem undo_001
binlog.000002 ca.pem '#ib_16384_0.dblwr' ibdata1 ibtmp1 mysql.ibd public_key.pem sys undo_002
# 删除pod并重新建立新的pod
[root@k8s-master01 ~]# kubectl delete deploy mysql
[root@k8s-master01 ~]# kubectl create -f mysql-deploy.yaml
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-6b4dcc8748-g9bxq 1/1 Running 0 50s
# 进入到新容器发现之前创建到库还在
[root@k8s-master01 ~]# kubectl exec -it mysql-6b4dcc8748-g9bxq -- bash
root@mysql-6b4dcc8748-g9bxq:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 8.0.20 MySQL Community Server - GPL
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
5 rows in set (0.01 sec)
mysql>
2.8.8 部署 Redis 并持久化数据
# 创建一个 Redis 的 PVC
[root@k8s-master01 ~]# vim redis-pvc.yaml
[root@k8s-master01 ~]# cat redis-pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-data
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-csi
# 创建一个 Redis 的 PVC
[root@k8s-master01 ~]# kubectl create -f redis-pvc.yaml
# 查看PVC
[root@k8s-master01 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
redis-data Bound pvc-8494b4c2-2341-4bc8-aeda-8edfa9713139 10Gi RWX nfs-csi <unset> 5s
# 查看PV
[root@k8s-master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-8494b4c2-2341-4bc8-aeda-8edfa9713139 10Gi RWX Delete Bound default/redis-data nfs-csi <unset> 6s
# 创建一个 Redis 的 Deploymen
[root@k8s-master01 ~]# vim redis-deploy.yaml
[root@k8s-master01 ~]# cat redis-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: redis
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
volumes:
- name: nfs
persistentVolumeClaim:
claimName: redis-data
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/redis:7.2.5
name: redis
ports:
- name: tcp-6379
containerPort: 6379
protocol: TCP
volumeMounts:
- name: nfs
mountPath: /data
livenessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 30
timeoutSeconds: 3
periodSeconds: 30
successThreshold: 1
failureThreshold: 2
readinessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 30
timeoutSeconds: 3
periodSeconds: 30
successThreshold: 1
failureThreshold: 2
imagePullPolicy: IfNotPresent
restartPolicy: Always
dnsPolicy: ClusterFirst
# 启动这个pod
[root@k8s-master01 ~]# kubectl create -f redis-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
redis-5c5bdd7995-5vxvf 1/1 Running 0 96s
# 登陆容器写入测试数据
[root@k8s-master01 ~]# kubectl exec -it redis-5c5bdd7995-5vxvf -- bash
root@redis-5c5bdd7995-5vxvf:/data# redis-cli
127.0.0.1:6379> set a 123
OK
127.0.0.1:6379> get a
"123"
127.0.0.1:6379> set b 456
OK
127.0.0.1:6379> get b
"456"
127.0.0.1:6379> save
OK
127.0.0.1:6379> exit
# 查看后端数据是否有数据写入
[root@harbor ~]# ls /data/nfs/pvc-8494b4c2-2341-4bc8-aeda-8edfa9713139/
dump.rdb
# 新增加一个pod副本
[root@k8s-master01 ~]# kubectl scale deploy redis --replicas=2
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
redis-5c5bdd7995-5vxvf 1/1 Running 0 2m10s
redis-5c5bdd7995-g6tzc 1/1 Running 0 43s
# 登陆新容器写入/修改测试数据
[root@k8s-master01 ~]# kubectl exec -it redis-5c5bdd7995-g6tzc -- bash
root@redis-5c5bdd7995-g6tzc:/data# redis-cli
127.0.0.1:6379> get a # 之前的数据还在
"123"
127.0.0.1:6379> get b
"456"
127.0.0.1:6379>
127.0.0.1:6379> set b 789 # 修改一个key的值
OK
127.0.0.1:6379> get b
"789"
127.0.0.1:6379> save
OK
127.0.0.1:6379> exit
# 回到第一个容器
[root@k8s-master01 ~]# kubectl exec -it redis-5c5bdd7995-5vxvf -- bash
root@redis-5c5bdd7995-5vxvf:/data# redis-cli
127.0.0.1:6379> get b # 发现数据没有被同步
"456"
127.0.0.1:6379>
# 删除pod重新创建
[root@k8s-master01 ~]# kubectl delete -f redis-deploy.yaml
[root@k8s-master01 ~]# kubectl create -f redis-deploy.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
redis-5c5bdd7995-sz2t9 1/1 Running 0 38s
# 登陆容器查看数据,不过是最新写入的
[root@k8s-master01 ~]# kubectl exec -it redis-5c5bdd7995-sz2t9 -- bash
root@redis-5c5bdd7995-sz2t9:/data# redis-cli
127.0.0.1:6379> get a
"123"
127.0.0.1:6379> get b
"789"
127.0.0.1:6379> exit
2.8.9 StatefulSet volumeClaimTemplates
使用 StatefulSet 部署有状态服务时,可以使用 volumeClaimTemplates 自动为每个 Pod 生成 PVC,并挂载至容器中,大大降低了手动创建管理存储的难度和复杂度。
假设需要搭建一个三节点的 RabbitMQ 集群到 K8s 中,并且需要实现数据的持久化,此时可以通过 StatefulSet 创建三个副本,并且通过 volumeClaimTemplates 绑定存储。
# 首先下载部署文件
[root@k8s-master01 ~]# git clone https://gitee.com/dukuan/k8s.git
# 更改存储配置
[root@k8s-master01 ~]# cd k8s/k8s-rabbitmq-cluster/
[root@k8s-master01 k8s-rabbitmq-cluster]# vim rabbitmq-cluster-ss.yaml
[root@k8s-master01 k8s-rabbitmq-cluster]# sed -n "86,92p;104,113p" rabbitmq-cluster-ss.yaml
volumeMounts:
- mountPath: /etc/rabbitmq
name: config-volume
readOnly: false
- mountPath: /var/lib/rabbitmq
name: rabbitmq-storage
readOnly: false
volumeClaimTemplates:
- metadata:
name: rabbitmq-storage
spec:
accessModes:
- ReadWriteMany
storageClassName: "nfs-csi"
resources:
requests:
storage: 4Gi
# 创建命名空间
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl create ns public-service
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get ns public-service
NAME STATUS AGE
public-service Active 12s
# 创建资源
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl create -f .
# 查看启动状态(依次创建)
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get po -n public-service
NAME READY STATUS RESTARTS AGE
rmq-cluster-0 1/1 Running 0 6m8s
rmq-cluster-1 1/1 Running 0 3m27s
rmq-cluster-2 1/1 Running 0 102s
查看创建的 PVC:
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get pvc -n public-service
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
rabbitmq-storage-rmq-cluster-0 Bound pvc-1f19eacd-bd71-4503-bb7a-179a904d7e9a 4Gi RWX nfs-csi <unset> 6m41s
rabbitmq-storage-rmq-cluster-1 Bound pvc-3583f5a6-47a0-420f-85b0-13cdf19b8fd8 4Gi RWX nfs-csi <unset> 3m57s
rabbitmq-storage-rmq-cluster-2 Bound pvc-f8cc5eac-575c-4f66-b22e-d40d7d20462c 4Gi RWX nfs-csi <unset> 2m12s
# 查看创建的 Service:
[root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get svc -n public-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
rmq-cluster ClusterIP None <none> 5672/TCP 7m38s
rmq-cluster-balancer NodePort 10.108.102.139 <none> 15672:31087/TCP,5672:30433/TCP 7m37s
通过 15672 端口的 NodePort 即可访问 RabbitMQ 的管理页面

2.8.10 Pod和PVC处于Pending的原因
- PVC一直Pending的原因
- PVC的空间申请大小大于PV的大小
- PVC的StorageClassName没有和PV的一致
- PVC的accessModes和PV的不一致
- 请求的PV已被其他的PVC绑定
- 挂载的PVC的Pod一直Pending的原因
- PVC没有创建成功/PVC不存在
- PVC和Pod不住同一个Namespace
3、K8s任务管理:Job和CronJob实践
3.1 Job
3.1.1 什么是Job
K8s的Job资源,主要用来执行单次任务,比如执行一次备份任务、微调一次模型等。对于每次任务,Job控制器会创建一个或多个Pod执行相关指令,并且确保成功的个数达到预期的值才会把Job标记为成功(Completed),否则将会标记为失败(Failed)
3.1.2 Job的特点与优势
- 环境隔离:Job可以使用不同的镜像执行任务,无需考虑版本冲突
- 单次执行:Job任务通常是短暂的,运行一次后就结束
- 状态追踪:Job控制器会追踪所有Pod的完成情况,一旦达到指定的成功次数,Job才会被标记为完成
- 失败重试:任务执行失败,可以按需重新执行
- 并行执行:同一个任务可以拆分不同的Pod并行执行,提高执行速度
3.1.3 Job创建
# 使用 Kubectl 创建一个一次性任务,打印一个 Hello
[root@k8s-master01 ~]# kubectl create job hello --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest -- echo "Hello, Job"
# 查看创建的 Job
[root@k8s-master01 ~]# kubectl get job
NAME STATUS COMPLETIONS DURATION AGE
hello Complete 1/1 17s 47s
# DURATION:表示 Job 从开始执行到最后一个 Pod 完成的时间长度
# COMPLETIONS:表示 Job 当前已完成的个数与期望完成次数
# 查看 Job 创建的 Pod
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-4t4hs 0/1 Completed 0 55s
# 查看执行日志
[root@k8s-master01 ~]# kubectl logs -f hello-4t4hs
Hello, Job
3.1.4 Job 并发执行
# 创建模板文件
[root@k8s-master01 ~]# kubectl create job hello-2 --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest --dry-run=client -oyaml -- echo "Hello, Job" > hello-2.yaml
# 修改配置
[root@k8s-master01 ~]# vim hello-2.yaml
[root@k8s-master01 ~]# cat hello-2.yaml
apiVersion: batch/v1
kind: Job
metadata:
creationTimestamp: null
name: hello-2
spec:
parallelism: 2 # 创建 2 个 Pod 同时执行任务
completions: 5 # 有 5 个完成表示任务结束
template:
metadata:
creationTimestamp: null
spec:
containers:
- command:
- sh
- -c
- echo "Hello, Job!"
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest
name: hello-2
resources: {}
restartPolicy: Never
status: {}
# parallelism:并行执行任务的数量。如果数值大于未完成任务的数量,只会创建未完成的数量
# completions:有多少Pod执行成功,认为任务是成功的
# 创建该 Job(一次性创建2个,一共创建5个)
[root@k8s-master01 ~]# kubectl create -f hello-2.yaml
# 查看创建的 Pod
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-tl88t 0/1 ContainerCreating 0 12s
hello-2-xqfjl 0/1 ContainerCreating 0 12s
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-msswf 0/1 ContainerCreating 0 5s
hello-2-tl88t 0/1 Completed 0 59s
hello-2-xffsz 0/1 ContainerCreating 0 4s
hello-2-xqfjl 0/1 Completed 0 59s
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-msswf 0/1 Completed 0 18s
hello-2-tl88t 0/1 Completed 0 72s
hello-2-x86w4 0/1 ContainerCreating 0 4s
hello-2-xffsz 0/1 Completed 0 17s
hello-2-xqfjl 0/1 Completed 0 72s
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-msswf 0/1 Completed 0 22s
hello-2-tl88t 0/1 Completed 0 76s
hello-2-x86w4 0/1 Completed 0 8s
hello-2-xffsz 0/1 Completed 0 21s
hello-2-xqfjl 0/1 Completed 0 76s
3.1.5 Job 重试机制
如果要实现 Pod 执行失败后可以重试,此时可以把重启策略改成 OnFailure,但是最好限制一下重试次数
# 比如最多允许每个 Pod 尝试两次任务执行
[root@k8s-master01 ~]# vim hello-3.yaml
[root@k8s-master01 ~]# cat hello-3.yaml
apiVersion: batch/v1
kind: Job
metadata:
creationTimestamp: null
name: hello-3
spec:
parallelism: 2 # 创建 2 个 Pod 同时执行任务
completions: 5 # 有 5 个完成表示任务结束
backoffLimit: 2 # 每个 Pod 最大重启次数
template:
metadata:
creationTimestamp: null
spec:
containers:
- command:
- sh
- -c
- echo "Hello, Job!" && sleep 1 && exit 42
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest
name: hello-3
resources: {}
restartPolicy: OnFailure
status: {}
# backoffLimit:如果任务执行失败,失败多少次后不再执行
# ttlSecondAfterFinished:Job在执行结束之后(状态为completed或者Failed)自动清理。设置为0表示执行结束立即删除,不设置则不会清除
# 创建该 Job
[root@k8s-master01 ~]# kubectl create -f hello-3.yaml
# 查看创建的 Pod(此时 Pod 会重启且最多重启两次)
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-3-4tr7m 0/1 ContainerCreating 0 6s
hello-3-gwpc8 0/1 ContainerCreating 0 6s
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-3-4tr7m 0/1 CrashLoopBackOff 1 (50s ago) 2m2s
hello-3-gwpc8 0/1 ErrImagePull 0 2m2s
# 重启结束后,Job 会被标记为失败
[root@k8s-master01 ~]# kubectl get job
NAME STATUS COMPLETIONS DURATION AGE
hello-3 Failed 0/5 5m14s 5m14s
3.2 CronJob
3.2.1 什么是CronJob
K8s是CronJob资源,是Job资源的延伸,主要用于周期性执行任务,比如定期备份数据库、定期对某个服务健康检查等。CronJob和Linux等计划任务功能类似,主要用于按计划执行某个操作,但功能比Linux远胜等计划任务更加丰富。
3.2.2 CronJob的特点与优势
- 周期执行:CronJob使用cron表达式定期创建Job执行任务
- 并行执行:CronJob具备多种并发策略,调用Job更加灵活
- 可靠性高:CronJob可以调度K8s的任意节点执行,防止单点故障
- 环境隔离:CronJob可以使用不同的镜像执行任务,无需考虑版本冲突
- 历史记录:CronJob可以保留多个成功和失败的任务,便于问题追踪和审计
3.2.3 CronJob 创建
# 创建一个每分钟执行一次的任务
[root@k8s-master01 ~]# kubectl create cj hello --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest --schedule='*/1 * * * *' -- echo "Hello, Hello from the Kubernetes CronJob"
# 查看创建的 CronJob
[root@k8s-master01 ~]# kubectl get cj
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * <none> False 0 <none> 10s
# SUSPEND:是否暂停调度
# ACTIVE:当前处于活跃的 Job 个数
# LAST SCHEDULE:上一次成功调度的时间
# TIMEZONE:CronJob 执行调度时所使用的时区,默认为 UTC
# 等待 1 分钟后,即可查看到调度的 Job
[root@k8s-master01 ~]# kubectl get job
NAME STATUS COMPLETIONS DURATION AGE
hello-29161974 Complete 1/1 6s 7s
3.2.4 CronJob 并发策略
CronJob 支持三种并发策略:
- Allow:允许同时运行多个任务,默认值
- Forbid:不允许并发运行,如果之前的任务尚未完成,新的任务不会被创建
- Replace:如果之前的任务尚未完成,新的任务会替换的之前的任务
如需更改并发策略,只需要更改 CronJob 的 concurrencyPolicy 字段即可。比如不允许 CronJob 并发执行:
# 修改配置
[root@k8s-master01 ~]# vim hello-2.yaml
[root@k8s-master01 ~]# cat hello-2.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: hello-2
spec:
schedule: '*/1 * * * *'
concurrencyPolicy: Forbid # 不允许并发运行,如果之前的任务尚未完成,新的任务不会被创建
jobTemplate:
metadata:
creationTimestamp: null
name: hello-2
spec:
template:
metadata:
creationTimestamp: null
spec:
containers:
- command:
- sh
- -c
- echo Hello, Hello from the Kubernetes CronJob; sleep 70
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/busybox:latest
name: hello-2
resources: {}
restartPolicy: OnFailure
# 创建该计划任务:
[root@k8s-master01 ~]# kubectl create -f hello-2.yaml
[root@k8s-master01 ~]# kubectl get cj
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
hello-2 */1 * * * * <none> False 0 <none> 4s
# 该任务在第一个创建 Job 后,第二个任务不会在70s后创建:
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-29162006-ztnhd 0/1 Completed 0 75s
hello-2-29162007-hcfpq 0/1 ContainerCreating 0 0s
# 第一次调度的 Pod 并未结束,同时由于并发策略的控制,下一次在 Pod 完成时不会调度
# 如果并发策略改成替换,下一次任务将覆盖上一次任务(避免长时间无法结束的任务)
[root@k8s-master01 ~]# vim hello-2.yaml
[root@k8s-master01 ~]# cat hello-2.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: hello-2
spec:
schedule: '*/1 * * * *'
concurrencyPolicy: Replace # 如果之前的任务尚未完成,新的任务会替换的之前的任务
....
# 重新创建该计划任务:
[root@k8s-master01 ~]# kubectl create -f hello-2.yaml
[root@k8s-master01 ~]# kubectl get cj
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
hello-2 */1 * * * * <none> False 0 <none> 7s
# 一分钟后,Pod 为结束,下一次的调度会覆盖上一次的任务
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-2-29162004-c7tqx 1/1 Terminating 0 60s
hello-2-29162005-w6kxp 0/1 ContainerCreating 0 0s
3.2.5 CronJob 执行记录
CronJob 默认的执行记录保留方式如下:
- 成功记录:默认为 3 次,可以通过 successfulJobsHistoryLimit 字段更改
- 失败记录:默认为 1 次,可以通过 failedJobsHistoryLimit 字段更改
为了更好的收集日志和追踪问题,可以增加记录的数量。比如都增加到 5 个
[root@k8s-master01 ~]# cat hello-2.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: hello-2
spec:
schedule: '*/1 * * * *'
failedJobsHistoryLimit: 5
successfulJobsHistoryLimit: 5
....
3.2.6 CronJob 调度时区
如果采用具体的时间调度任务,需要注意调度的时区问题。
如果 CronJob 未标注调度时区,Kubernetes 会以 kube-controller-manager 组件的时区进行调度,如果该组件运行的时区和本地时区不一样,会导致无法按照规定时间进行调度。
比如创建一个每天凌晨一点开始执行的任务,此时配置的调度表达式可能如下:
schedule: '00 01 * * *'
但是如果未指定时区,当 kube-controller-manager 组件的时区和本地时区有差别时,可能会在每天的九点进行调度(本地时区为 Shanghai 时区,kube-controller-manager 采用 UTC 时间)
在 Kubernetes 1.25 版本时,CronJob 增加了.spec.timeZone 的字段用于配置 CronJob 的调
度时区,在 1.27 版本达到稳定,可以直接使用。1.25~1.27 版本之间需要打开 kube-apiserver 的featuregates 特性,比如:--feature-gates=CronJobTimeZone=true
配置时区只需要添加 timeZone 即可:
[root@k8s-master01 ~]# cat hello-2.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: hello-2
spec:
schedule: '*/1 * * * *'
timeZone: "Asia/Shanghai"
...
3.3 任务管理实战
3.3.1 使用 CronJob 定期备份 MySQL
# 创建一个测试的 MySQL
[root@k8s-master01 ~]# kubectl create deploy mysql --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/mysql:8.0.20 --dry-run=client -oyaml > mysql-deploy.yaml
[root@k8s-master01 ~]# vim mysql-deploy.yaml
[root@k8s-master01 ~]# cat mysql-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: mysql
name: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/mysql:8.0.20
name: mysql
ports:
- name: tcp-3306
containerPort: 3306
protocol: TCP
env:
- name: MYSQL_ROOT_PASSWORD
value: password_123
# 创建
[root@k8s-master01 ~]# kubectl create -f mysql-deploy.yaml
deployment.apps/mysql created
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
mysql-86c9b6bb4c-wsrc2 1/1 Running 0 3s
# 创建一个 MySQL 的 Service
[root@k8s-master01 ~]# kubectl expose deploy mysql --port 3306
service/mysql exposed
[root@k8s-master01 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
....
mysql ClusterIP 10.106.69.170 <none> 3306/TCP 7s
# 创建一个测试库和表
[root@k8s-master01 ~]# kubectl exec -it mysql-86c9b6bb4c-wsrc2 -- bash
root@mysql-86c9b6bb4c-wsrc2:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.20 MySQL Community Server - GPL
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> create database test;
Query OK, 1 row affected (0.04 sec)
mysql> use test;
Database changed
mysql> CREATE TABLE employees (id INT AUTO_INCREMENT PRIMARY KEY,first_name VARCHAR(50) NOT NULL,last_name VARCHAR(50) NOT NULL,email VARCHAR(100),hire_date DATE);
Query OK, 0 rows affected (0.02 sec)
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| employees |
+----------------+
1 row in set (0.00 sec)
mysql>
[root@k8s-master01 csi-driver-nfs]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-csi nfs.csi.k8s.io Delete Immediate false 12h
# 创建备份的持久化 PVC
[root@k8s-master01 ~]# vim mysql-pv.yaml
[root@k8s-master01 ~]# cat mysql-pv.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-backup-data
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-csi
[root@k8s-master01 ~]# kubectl create -f mysql-pv.yaml
[root@k8s-master01 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
mysql-backup-data Bound pvc-daa50aa2-a773-4cef-874a-1551d77b2b29 10Gi RWX nfs-csi <unset> 4s
[root@k8s-master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-daa50aa2-a773-4cef-874a-1551d77b2b29 10Gi RWX Delete Bound default/mysql-backup-data nfs-csi <unset> 5s
# 创建 CronJob 每天凌晨 1 点执行备份
[root@k8s-master01 ~]# vim mysql-backup.yaml
[root@k8s-master01 ~]# cat mysql-backup.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: mysql-backup
spec:
schedule: '0 1 * * *'
timeZone: "Asia/Shanghai"
failedJobsHistoryLimit: 5
successfulJobsHistoryLimit: 5
jobTemplate:
spec:
template:
metadata:
creationTimestamp: null
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: mysql-backup-data
restartPolicy: Never
containers:
- command:
- sh
- -c
- |
mysqldump -hmysql.default -P3306 -uroot -p'password_123' --all-databases > /mnt/all-`date +%Y%m%d-%H%M%S`.sql;
ls /mnt
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/mysql:8.0.20
name: mysql-backup
volumeMounts:
- name: data
mountPath: /mnt
# 创建cronJob
[root@k8s-master01 ~]# kubectl create -f mysql-backup.yaml
[root@k8s-master01 ~]# kubectl get cj
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
mysql-backup 0 1 * * * <none> False 0 <none> 3s
# 模拟启动测试
# 启动一个叫mysql-backup-test的job,指定来源与我们建好的cronjob
[root@k8s-master01 ~]# kubectl create job mysql-backup-test --from=cronjob/mysql-backup
# 查看job
[root@k8s-master01 ~]# kubectl get job
NAME STATUS COMPLETIONS DURATION AGE
mysql-backup-test Complete 1/1 6s 28s
# 查看pod
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-backup-test-9tln7 0/1 Completed 0 30s
# 查看日志是否备份
[root@k8s-master01 ~]# kubectl logs -f mysql-backup-test-9tln7
mysqldump: [Warning] Using a password on the command line interface can be insecure.
3.3.2 使用 CronJob 定期重启 K8s 服务
有时候需要定期重启 K8s 中的服务,也可以使用 CronJob 实现
# 创建一个用于放置 CronJob 的命名空间
[root@k8s-master01 ~]# kubectl create ns cronjob
# 创建重启 K8s 服务的权限
# 创建 ClusterRole
[root@k8s-master01 ~]# kubectl create clusterrole deployment-restart --verb=get,update,patch --resource=deployments.apps
# 绑定权限
[root@k8s-master01 ~]# kubectl create clusterrolebinding deployment-restart-binding --clusterrole=deployment-restart --serviceaccount=cronjob:default
# 创建 CronJob 执行重启任务(每天凌晨一点执行)
[root@k8s-master01 ~]# vim mysql-restart.yaml
[root@k8s-master01 ~]# cat mysql-restart.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: mysql-restart
spec:
schedule: '0 1 * * *'
timeZone: "Asia/Shanghai"
failedJobsHistoryLimit: 3
successfulJobsHistoryLimit: 3
concurrencyPolicy: Allow
suspend: false
jobTemplate:
spec:
template:
metadata:
creationTimestamp: null
spec:
restartPolicy: Never
containers:
- command:
- /bin/bash
- -c
- >-
kubectl rollout restart deploy mysql -n default
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kubectl:latest
name: mysql-restart
# 创建 CronJob:
[root@k8s-master01 ~]# kubectl create -f mysql-restart.yaml -n cronjob
[root@k8s-master01 ~]# kubectl get cj -n cronjob
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
mysql-restart 0 1 * * * Asia/Shanghai False 0 <none> 21s
# 模拟启动测试
# 启动一个叫mysql-backup-test的job,指定来源与我们建好的cronjob
[root@k8s-master01 ~]# kubectl create job mysql-restart-test --from=cronjob/mysql-restart -n cronjob
# 查看job
[root@k8s-master01 ~]# kubectl get job -n cronjob
NAME STATUS COMPLETIONS DURATION AGE
mysql-restart-test Complete 1/1 6s 20s
# 查看pod及日志
[root@k8s-master01 ~]# kubectl get pod -n cronjob
NAME READY STATUS RESTARTS AGE
mysql-restart-test-jtjdg 0/1 Completed 0 31s
[root@k8s-master01 ~]# kubectl logs -f mysql-restart-test-jtjdg -n cronjob
# 测试pod已经重启
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-5b9499c559-gjlt9 1/1 Running 0 66s

浙公网安备 33010602011771号