六、K8s管理

六、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中的一个或多个容器中,从而实现容器的数据持久化和数据共享。

image.png-260.6kB

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存储管理架构

image.png-2486.9kB

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 的管理页面

image.png-15.6kB

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管理

目录

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中的一个或多个容器中,从而实现容器的数据持久化和数据共享。

image.png-260.6kB

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存储管理架构

image.png-2486.9kB

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 的管理页面

image.png-15.6kB

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

此博客来源于:https://edu.51cto.com/lecturer/11062970.html