Kubernetes之存储

1、ConfigMap

  • ConfigMap功能在Kubernetes1.2版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API给我们提供了向容器中注入配置信息的机制,ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制大对象。

1.1 ConfigMap的创建

  • ①使用目录创建。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    [root@master ~]# mkdir configmap
    [root@master ~]# cd configmap/
    [root@master configmap]# vim fruit.properties
    [root@master configmap]# cat fruit.properties
    apple=8.0
    orange=3.5
    [root@master configmap]# vim meat.properties
    [root@master configmap]# cat meat.properties
    beef=55.0
    pork=28.0
    # 使用目录创建ConfigMap,shop-config:ConfigMap的名称,--from-file:配置文件所在位置
    [root@master configmap]# kubectl create configmap shop-config --from-file=.
    configmap/shop-config created
    [root@master configmap]# kubectl get cm
    NAME DATA AGE
    shop-config 2 12s
    [root@master configmap]# kubectl get cm shop-config -o yaml
    apiVersion: v1
    data:
    fruit.properties: |
    apple=8.0
    orange=3.5
    meat.properties: |
    beef=55.0
    pork=28.0
    kind: ConfigMap
    metadata:
    creationTimestamp: "2021-10-16T02:50:43Z"
    name: shop-config
    namespace: default
    resourceVersion: "91752"
    selfLink: /api/v1/namespaces/default/configmaps/shop-config
    uid: 87602016-cd59-4da4-8f85-1359d66fde4d
    [root@master configmap]# kubectl describe cm shop-config
    Name: shop-config
    Namespace: default
    Labels: <none>
    Annotations: <none>

    Data
    ====
    fruit.properties:
    ----
    apple=8.0
    orange=3.5

    meat.properties:
    ----
    beef=55.0
    pork=28.0

    Events: <none>
  • ②使用文件创建。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    [root@master configmap]# vim drink.properties
    [root@master configmap]# cat drink.properties
    tea=3.0
    coffee=4.0
    # --from-file这个参数可以使用多次
    [root@master configmap]# kubectl create configmap drink-config --from-file=./drink.properties
    configmap/drink-config created
    [root@master configmap]# kubectl get cm drink-config -o yaml
    apiVersion: v1
    data:
    drink.properties: |
    tea=3.0
    coffee=4.0
    kind: ConfigMap
    metadata:
    creationTimestamp: "2021-10-16T03:08:40Z"
    name: drink-config
    namespace: default
    resourceVersion: "93439"
    selfLink: /api/v1/namespaces/default/configmaps/drink-config
    uid: 84833a91-5726-4973-a6ef-4740ce632528
  • ③使用字面值创建。

    • 使用文字值创建,利用–from-literal参数传递配置信息,该参数可以使用多次,格式如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      [root@master configmap]# kubectl create configmap snacks-config --from-literal=chips=6.9 --from-literal=noodles=4.0
      configmap/snacks-config created
      [root@master configmap]# kubectl get cm snacks-config -o yaml
      apiVersion: v1
      data:
      chips: "6.9"
      noodles: "4.0"
      kind: ConfigMap
      metadata:
      creationTimestamp: "2021-10-16T03:20:28Z"
      name: snacks-config
      namespace: default
      resourceVersion: "94554"
      selfLink: /api/v1/namespaces/default/configmaps/snacks-config
      uid: 916967ce-bfda-46d2-83cd-78fc64d45196

1.2 使用ConfigMap

  • 使用ConfigMap有三种方式,一种是通过环境变量的方式,直接传递pod;另一种是通过在pod的命令行下运行的方式;第三种是使用volume的方式挂载入到pod内。

    • ①使用ConfigMap来替代环境变量。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      [root@master env]# vim animal.yaml
      [root@master env]# cat animal.yaml
      apiVersion: v1
      kind: ConfigMap
      metadata:
      name: animal-config
      namespace: default
      data:
      cat: cute # 这里只能使用字符串
      dog: lovely
      [root@master env]# kubectl apply -f animal.yaml
      configmap/animal-config created
      [root@master env]# vim use-configmap.yaml
      [root@master env]# cat use-configmap.yaml
      apiVersion: v1
      kind: Pod
      metadata:
      name: use-configmap-pod
      spec:
      containers:
      - name: test-container
      image: nginx:latest
      command: [ "/bin/sh", "-c", "env" ]
      env:
      - name: ANIMAL-CONFIG-CAT
      valueFrom:
      configMapKeyRef:
      name: animal-config
      key: cat
      - name: ANIMAL-CONFIG-DOG
      valueFrom:
      configMapKeyRef:
      name: animal-config
      key: dog
      envFrom: # 第二种导入方案
      - configMapRef:
      name: snacks-config
      restartPolicy: Never
      [root@master env]# kubectl apply -f use-configmap.yaml
      pod/use-configmap-pod created
      [root@master env]# kubectl get pod
      NAME READY STATUS RESTARTS AGE
      use-configmap-pod 0/1 Completed 0 62s
      [root@master env]# kubectl log use-configmap-pod
      log is DEPRECATED and will be removed in a future version. Use logs instead.
      KUBERNETES_SERVICE_PORT=443
      KUBERNETES_PORT=tcp://10.96.0.1:443
      HOSTNAME=use-configmap-pod
      HOME=/root
      PKG_RELEASE=1~buster
      chips=6.9
      KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
      NGINX_VERSION=1.21.3
      noodles=4.0
      PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      KUBERNETES_PORT_443_TCP_PORT=443
      NJS_VERSION=0.6.2
      KUBERNETES_PORT_443_TCP_PROTO=tcp
      KUBERNETES_SERVICE_PORT_HTTPS=443
      KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
      KUBERNETES_SERVICE_HOST=10.96.0.1
      PWD=/
    • ②用ConfigMap设置命令行参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    [root@master env]# vim use-configmap2.yaml
    [root@master env]# cat use-configmap2.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: use-configmap-pod2
    spec:
    containers:
    - name: test-container
    image: nginx:latest
    command: [ "/bin/sh", "-c", "echo $(ANIMAL-CONFIG-CAT) $(ANIMAL-CONFIG-DOG)" ]
    env:
    - name: ANIMAL-CONFIG-CAT
    valueFrom:
    configMapKeyRef:
    name: animal-config
    key: cat
    - name: ANIMAL-CONFIG-DOG
    valueFrom:
    configMapKeyRef:
    name: animal-config
    key: dog
    restartPolicy: Never
    [root@master env]# kubectl apply -f use-configmap2.yaml
    pod/use-configmap-pod2 created
    [root@master env]# kubectl get pod
    NAME READY STATUS RESTARTS AGE
    use-configmap-pod2 0/1 Completed 0 7s
    [root@master env]# kubectl log use-configmap-pod2
    log is DEPRECATED and will be removed in a future version. Use logs instead.
    cute lovely
    • ③通过数据卷插件使用ConfigMap。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      [root@master env]# vim use-configmap3.yaml
      [root@master env]# cat use-configmap3.yaml
      apiVersion: v1
      kind: Pod
      metadata:
      name: use-configmap-pod3
      spec:
      containers:
      - name: test-container
      image: nginx:v1
      command: [ "/bin/sh", "-c", "ls /etc/config" ]
      volumeMounts:
      - name: config-volume
      mountPath: /etc/config
      volumes:
      - name: config-volume
      configMap:
      name: animal-config
      restartPolicy: Never
      [root@master env]# kubectl apply -f use-configmap3.yaml
      pod/use-configmap-pod3 created
      [root@master env]# kubectl get pod
      NAME READY STATUS RESTARTS AGE
      use-configmap-pod3 0/1 Completed 0 8s
      [root@master env]# kubectl log use-configmap-pod3 # key为文件名,value为文件内容
      log is DEPRECATED and will be removed in a future version. Use logs instead.
      cat
      dog

1.3 ConfigMap热更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[root@master env]# cat hot-deploy.yaml 
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx:v1
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: snacks-config
[root@master env]# kubectl apply -f hot-deploy.yaml
deployment.extensions/my-nginx created
[root@master env]# kubectl get pod
NAME READY STATUS RESTARTS AGE
my-nginx-597c67865f-jqws2 1/1 Running 0 42s
use-configmap-pod3 0/1 Completed 0 6m43s
[root@master env]# kubectl exec -it my-nginx-597c67865f-jqws2 -- cat /etc/config/chips
6.9
# 修改configMap中chips的值为7.9
[root@master env]# kubectl edit cm snacks-config
configmap/snacks-config edited
# 经过一段时间后再次查看容器中的chips值,发现已自动变成最新值
[root@master env]# kubectl exec -it my-nginx-597c67865f-jqws2 -- cat /etc/config/chips
7.9
  • 注意:

    • ①更新ConfigMap目前并不会触发相关Pod的滚动更新,可以通过修改pod annotations的方式强制触发滚动更新:

      1
      $ kubectl patch deployment my-nginx --patch '{"spec":{"template":{"metadata":{"annotations":{"version/config":"20190411" }}}}}'
    • ②更新ConfigMap后:

      • 使用该ConfigMap挂载的Env不会同步更新。
      • 使用该ConfigMap挂载的Volume中的数据需要一段时间(实测大概10秒)才能同步更新。

2、Secret

  • Secret解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者Pod Spec中。Secret可以以Volume或者环境变量的方式使用。
  • Secret有三种类型:
    • Service Account:用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中。
    • Opaque:base64编码格式的Secert,用来存储密码、密钥等。
    • kubernetes.io/dockerconfigjson:用来存储私有docker registry的认证信息。

2.1 Service Account

1
2
3
4
[root@master env]# kubectl exec -it my-nginx-597c67865f-jqws2 -- /bin/sh
# cd /run/secrets/kubernetes.io/serviceaccount
# ls
ca.crt namespace token

2.2 Opaque

  • Opaque类型的数据是一个map类型,要求value是base64编码格式:

    1
    2
    3
    4
    [root@master ~]# echo -n "admin" | base64
    YWRtaW4=
    [root@master ~]# echo -n "1f2d1e2e67df" | base64
    MWYyZDFlMmU2N2Rm
  • 创建Opaque类型的secret:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [root@master ~]# vim secert.yaml
    [root@master ~]# cat secert.yaml
    apiVersion: v1
    kind: Secret
    metadata:
    name: mysecret
    type: Opaque
    data:
    username: YWRtaW4=
    password: MWYyZDFlMmU2N2Rm
    [root@master ~]# kubectl apply -f secert.yaml
    secret/mysecret created
    [root@master ~]# kubectl get secret
    NAME TYPE DATA AGE
    basic-auth Opaque 1 22h
    default-token-4q5xv kubernetes.io/service-account-token 3 10d
    mysecret Opaque 2 7s
    tls-secret kubernetes.io/tls 2 22h
  • 将Secret挂载到Volume中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    [root@master ~]# vim opaque-volume.yaml 
    [root@master ~]# cat opaque-volume.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    labels:
    name: secret-test
    name: secret-test
    spec:
    volumes:
    - name: secrets
    secret:
    secretName: mysecret
    containers:
    - image: nginx:v1
    name: db
    volumeMounts:
    - name: secrets
    mountPath: /etc/config
    [root@master ~]# kubectl create -f opaque-volume.yaml
    pod/secret-test created
    [root@master ~]# kubectl get pod
    NAME READY STATUS RESTARTS AGE
    secret-test 1/1 Running 0 14s
    [root@master ~]# kubectl exec -it secret-test -- /bin/sh
    # cd /etc/config/
    # ls
    password username
    # cat password && echo && cat username
    1f2d1e2e67df
    admin
  • 将Secret导出到环境变量中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    [root@master ~]# vim opaque-env.yaml 
    [root@master ~]# cat opaque-env.yaml
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
    name: pod-deployment
    spec:
    replicas: 2
    template:
    metadata:
    labels:
    app: pod-deployment
    spec:
    containers:
    - name: pod-1
    image: nginx:v1
    ports:
    - containerPort: 80
    env:
    - name: TEST_USER
    valueFrom:
    secretKeyRef:
    name: mysecret
    key: username
    - name: TEST_PASSWORD
    valueFrom:
    secretKeyRef:
    name: mysecret
    key: password
    [root@master ~]# kubectl apply -f opaque-env.yaml
    deployment.extensions/pod-deployment created
    [root@master ~]# kubectl get pod
    NAME READY STATUS RESTARTS AGE
    pod-deployment-6b745bc765-777pk 1/1 Running 0 6s
    pod-deployment-6b745bc765-gbfpw 1/1 Running 0 6s
    [root@master ~]# kubectl exec pod-deployment-6b745bc765-777pk -it -- /bin/sh
    # echo $TEST_USER
    admin
    # echo $TEST_PASSWORD
    1f2d1e2e67df

2.3 kubernetes.io/dockerconfigjson

  • 使用Kuberctl创建docker registry认证的secret。

    1
    2
    [root@master secert]# kubectl create secret docker-registry myregistrykey --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
    secret "myregistrykey" created
  • 在创建Pod的时候,通过imagePullSecrets来引用刚创建的myregistrykey。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [root@master secert]# vim my-secret-pod.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: foo
    spec:
    containers:
    - name: foo
    image: nginx:v1
    imagePullSecrets:
    - name: myregistrykey

3、Volume

  • 容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。首先,当容器崩溃时,kubelet会重启它,但是容器中的文件将丢失,容器以干净的状态(镜像最初的状态)重新启动。其次,在Pod中同时运行多个容器时,这些容器之间通常需要共享文件。Kubernetes中的Volume抽象就很好的解决了这些问题。

  • Kubernetes中的卷(Volume)有明确的寿命,与封装它的Pod相同。所f以,卷的生命比Pod中的所有容器都长,当这个容器重启时数据仍然得以保存。当然,当Pod不再存在时,卷也将不复存在。Kubernetes支持多种类型的卷,Pod可以同时使用任意数量的卷。

    1634437636817
  • Kubernetes支持以下类型的卷:

    • awsElasticBlockStore、azureDisk、azureFile、cephfs、csi、downwardAPI、emptyDir
    • fc、flocker、gcePersistentDisk、gitRepo、glusterfs、hostPath、iscsi、local、nfs
    • persistentVolumeClaim、projected、portworxVolume、quobyte、rbd、scaleIO、secret
    • storageos、vsphereVolume

3.1 emptyDir

  • 当Pod被分配给节点时,首先创建emptyDir卷,并且只要该Pod在该节点上运行,该卷就会存在。正如卷的名 字所述,它最初是空的。Pod中的容器可以读取和写入emptyDir卷中的相同文件,尽管该卷可以挂载到每个容器中的相同或不同路径上。当出于任何原因从节点中删除Pod时,emptyDir中的数据将被永久删除。
  • emptyDir的用法有:
    • 暂存空间,例如用于基于磁盘的合并排序。
    • 用作长时间计算崩溃恢复时的检查点。
    • Web服务器容器提供数据时,保存内容管理器容器提取的文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 同步时间
[root@master ~]# ntpdate ntp1.aliyun.com
17 Oct 10:53:14 ntpdate[23763]: adjust time server 120.25.115.20 offset -0.000880 sec
[root@master ~]# vim ed.yaml
[root@master ~]# cat ed.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: nginx:v1
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
- image: busybox
name: test-container2
imagePullPolicy: IfNotPresent
command: ['/bin/sh','-c','sleep 3600']
volumeMounts:
- mountPath: /test
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
[root@master ~]# kubectl apply -f ed.yaml
pod/test-pd created
[root@master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
test-pd 2/2 Running 0 5s
# 进入第一个容器的挂载目录/cache下创建一个date.log文件
[root@master ~]# kubectl exec test-pd -c test-container -it -- /bin/sh
# cd cache/
# date > date.log
# cat date.log
Sun Oct 17 03:12:11 UTC 2021
# exit

# 进入第二个容器的挂载目录/test查看date.log文件
[root@master ~]# kubectl exec test-pd -c test-container2 -it -- /bin/sh
/ # cd test/
/test # cat date.log
Sun Oct 17 03:12:11 UTC 2021

3.2 hostPath

  • hostPath卷将主机节点的文件系统中的文件或目录挂载到集群中。

    1634441255725
  • hostPath的用途如下:

    • 运行需要访问Docker内部的容器;使用/var/lib/docker的hostPath。
    • 在容器中运行cAdvisor;使用/dev/cgroups的hostPath。
    • 允许pod指定给定的hostPath是否应该在pod运行之前存在,是否应该创建,以及它应该以什么形式存在。
  • 除了所需的path属性之外,用户还可以为hostPath卷指定type:

    行为
    空字符串(默认)用于向后兼容,这意味着在挂载hostPath卷之前不会执行任何检查。
    DirectoryOrCreate 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为0755,与Kubelet具有相同的组和所有权。
    Directory 给定的路径下必须存在目录
    FileOrCreate 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设 置为0644,与Kubelet具有相同的组和所有权。
    File 给定的路径下必须存在文件。
    Socket 给定的路径下必须存在UNIX套接字。
    CharDevice 给定的路径下必须存在字符设备。
    BlockDevice 给定的路径下必须存在块设备。
    • 使用这种卷类型是请注意,因为:
      • 由于每个节点上的文件都不同,具有相同配置(例如从 podTemplate 创建的)的pod在不同节点上的行为可能会有所不同。
      • 当Kubernetes按照计划添加资源感知调度时,将无法考虑hostPath使用的资源。
      • 在底层主机上创建的文件或目录只能由root写入。您需要在特权容器中以root身份运行进程,或修改主机上的文件权限以便写入hostPath卷。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[root@master ~]# vim hostPath.yaml
[root@master ~]# cat hostPath.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: nginx:v1
name: test-container
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /data
type: Director

# 分别在node1和node2上创建/data目录
[root@node1 ~]# mkdir /data
[root@node2 ~]# mkdir /data

[root@master ~]# kubectl apply -f hostPath.yaml
pod/test-pd created
[root@master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-pd 1/1 Running 0 4m16s 10.244.1.72 node1 <none> <none>
[root@master ~]# kubectl exec test-pd -it -- /bin/sh
# cd test-pd/
# date > date.log
# cat date.log
Sun Oct 17 03:43:59 UTC 2021

# 可以在node1工作节点看到创建的文件
[root@node1 ~]# cd /data
[root@node1 data]# cat date.log
Sun Oct 17 03:43:59 UTC 2021

4、PV-PVC

4.1 概念

  • PersistentVolume(PV):是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV也是集群中的资源。PV是Volume之类的卷插件,但具有独立于使用PV的Pod的生命周期。此API对象包含存储实现的细节,即NFS、iSCSI或特定于云供应商的存储系统。
  • PersistentVolumeClaim(PVC): 是用户存储的请求,它与Pod相似。Pod消耗节点资源,PVC消耗PV资源。Pod可以请求特定级别的资源(CPU和内存)。声明可以请求特定的大小和访问模式(例如,可以以读/写一次或只读多次模式挂载)。
  • 静态pv:集群管理员创建一些PV,它们带有可供群集用户使用的实际存储的细节。它们存在于Kubernetes API中,可用于消费。
  • 动态:当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,集群可能会尝试动态地为PVC创建卷。此配置基于StorageClasses:PVC必须请求 [存储类],并且管理员必须创建并配置该类才能进行动态创建。声明该类为””可以有效地禁用其动态配置要启用基于存储级别的动态存储配置,集群管理员需要启用API server上的DefaultStorageClass[准入控制器]。例如,通过确保DefaultStorageClass位于API server组件的–admission-control标志,使用逗号分隔的有序值列表中,可以完成此操作。
  • 绑定:master中的控制环路监视新的PVC,寻找匹配的PV(如果可能),并将它们绑定在一起。如果为新的PVC动态调配PV,则该环路将始终将该PV绑定到PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。一旦PV和PVC绑定后,PersistentVolumeClaim绑定是排他性的,不管它们是如何绑定的。 PVC跟PV绑定是一对一的映射。
  • 持久化卷声明的保护:PVC保护的目的是确保由pod正在使用的PVC不会从系统中移除,因为如果被移除的话可能会导致数据丢失。当启用PVC保护alpha功能时,如果用户删除了一个pod正在使用的PVC,则该PVC不会被立即删除。PVC的删除将被推迟,直到PVC不再被任何pod使用。

4.2 PV说明

4.2.1 持久化卷类型

  • PV类型以插件形式实现。K8S目前支持以下插件类型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    apiVersion: v1
    kind: PersistentVolumemeta
    data:
    name: pv0003
    spec:
    capacity:
    storage: 5Gi
    volumeMode: Filesystem
    accessModes:
    - ReadWriteOnce
    persistentVolumeReclaimPolicy: Recycle
    storageClassName: slow
    mountOptions:
    - hard
    - nfsvers=4.1
    nfs:
    path: /tmp
    server: 172.17.0.2

4.2.2 PV访问模式

  • PV可以以资源提供者支持的任何方式挂载到主机上。如下表所示,供应商具有不同的功能,每个PV的访问模式都将被设置为该卷支持的特定模式。例如,NFS可以支持多个读/写客户端,但特定的NFS PV可能以只读方式导出到服务器上。每个PV都有一套自己的用来描述特定功能的访问模式:

    • ReadWriteOnce:该卷可以被单个节点以读/写模式挂载。
    • ReadOnlyMany:该卷可以被多个节点以只读模式挂载。
    • ReadWriteMany:该卷可以被多个节点以读/写模式挂载。
  • 在命令行中,访问模式缩写为:

    • RWOReadWriteOnce

    • ROXReadOnlyMany

    • RWXReadWriteMany

4.2.3 回收策略

  • Retain(保留)——手动回收。
  • Recycle(回收)——基本擦除(rm -rf /thevolume/*
  • Delete(删除)——关联的存储资产(例如 AWS EBSGCE PDAzure DiskOpenStack Cinder卷)将被删除。
    • 当前,只有NFS和HostPath支持回收(Recycle)策略。AWS EBS、GCE PD、Azure Disk和Cinder卷支持删除策略。

4.2.4 状态

  • 卷可以处于以下的某种状态:

    • Available(可用)——一块空闲资源还没有被任何声明绑定。
    • Bound(已绑定)——卷已经被声明绑定。
    • Released(已释放)——声明被删除,但是资源还未被集群重新声明。
    • Failed(失败)——该卷的自动回收失败。
  • 命令行会显示绑定到PV的PVC的名称。

4.3 持久化演示说明-NFS

  • ①安装NFS服务器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [root@centos7-01 ~]# yum install -y nfs-common nfs-utils rpcbind
    [root@centos7-01 ~]# mkdir /nfs && chmod 777 /nfs && chown nfsnobody /nfs
    [root@centos7-01 ~]# vim /etc/exports
    [root@centos7-01 ~]# cat /etc/exports
    /nfs *(rw,no_root_squash,no_all_squash,sync)
    [root@centos7-01 ~]# systemctl start rpcbind && systemctl start nfs

    # 所有的k8s节点安装nfs所需要的依赖包
    [root@master ~]# yum install -y nfs-common nfs-utils rpcbind
    [root@node1 ~]# yum install -y nfs-common nfs-utils rpcbind
    [root@node2 ~]# yum install -y nfs-common nfs-utils rpcbind
    # 测试主节点目录挂载nfs
    [root@master ~]# showmount -e 192.168.200.130
    Export list for 192.168.200.130:
    /nfs *
    [root@master ~]# mkdir /test && mount -t nfs 192.168.200.130:/nfs /test
    [root@master ~]# umount /test
  • ②部署PV。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    [root@master ~]# vim test-pv.yaml
    [root@master ~]# cat test-pv.yaml
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: nfs-pv1
    spec:
    capacity:
    storage: 1Gi
    accessModes:
    - ReadWriteOnce
    persistentVolumeReclaimPolicy: Retain
    storageClassName: nfs
    nfs:
    path: /nfs
    server: 192.168.200.130
    [root@master ~]# kubectl create -f test-pv.yaml
    persistentvolume/nfs-pv1 created
    [root@master ~]# kubectl get pv
    NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
    nfs-pv1 1Gi RWO Retain Available nfs 8s
  • ③创建服务并使用PVC。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    # 新增两个挂载点
    [root@centos7-01 ~]# mkdir /nfs{2..3} && chmod 777 /nfs2/ /nfs3
    [root@centos7-01 ~]# chown nfsnobody /nfs2/ /nfs3
    [root@centos7-01 ~]# vim /etc/exports
    [root@centos7-01 ~]# cat /etc/exports
    /nfs *(rw,no_root_squash,no_all_squash,sync)
    /nfs2 *(rw,no_root_squash,no_all_squash,sync)
    /nfs3 *(rw,no_root_squash,no_all_squash,sync)
    [root@centos7-01 ~]# systemctl restart rpcbind && systemctl restart nfs

    [root@master ~]# vim test-pv.yaml
    [root@master ~]# cat test-pv.yaml
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: nfs-pv1
    spec:
    capacity:
    storage: 1Gi
    accessModes:
    - ReadWriteOnce
    persistentVolumeReclaimPolicy: Retain
    storageClassName: nfs
    nfs:
    path: /nfs
    server: 192.168.200.130
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: nfs-pv2
    spec:
    capacity:
    storage: 5Gi
    accessModes:
    - ReadOnlyMany
    persistentVolumeReclaimPolicy: Retain
    storageClassName: nfs
    nfs:
    path: /nfs2
    server: 192.168.200.130
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: nfs-pv3
    spec:
    capacity:
    storage: 1Gi
    accessModes:
    - ReadWriteMany
    persistentVolumeReclaimPolicy: Retain
    storageClassName: slow
    nfs:
    path: /nfs3
    server: 192.168.200.130
    [root@master ~]# kubectl create -f test-pv.yaml
    persistentvolume/nfs-pv1 created
    persistentvolume/nfs-pv2 created
    persistentvolume/nfs-pv3 created
    [root@master ~]# kubectl get pv
    NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
    nfs-pv1 1Gi RWO Retain Available nfs 4s
    nfs-pv2 5Gi ROX Retain Available nfs 4s
    nfs-pv3 1Gi RWX Retain Available slow 4s

    # 创建pod并使用pvc
    [root@master ~]# vim pod.yaml
    [root@master ~]# cat pod.yaml
    apiVersion: v1
    kind: Service
    metadata:
    name: nginx
    labels:
    app: nginx
    spec:
    ports:
    - port: 80
    name: web
    clusterIP: None
    selector:
    app: nginx
    ---
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
    name: web
    spec:
    selector:
    matchLabels:
    app: nginx
    serviceName: "nginx"
    replicas: 3
    template:
    metadata:
    labels:
    app: nginx
    spec:
    containers:
    - name: nginx
    image: nginx:v1
    ports:
    - containerPort: 80
    name: web
    volumeMounts:
    - name: www
    mountPath: /usr/share/nginx/html
    volumeClaimTemplates: # pvc的定义
    - metadata:
    name: www
    spec:
    accessModes: ["ReadWriteOnce"]
    storageClassName: "nfs"
    resources:
    requests:
    storage: 1Gi
    [root@master ~]# kubectl apply -f pod.yaml
    service/nginx created
    statefulset.apps/web created
    # 由于同时满足accessModes、storageClassName和storage(大于或等于的pv即可,优先选择资源少的)的只有一个PV对应,所以只有web-0正常运行而web-1被挂起
    # 且由于StatefulSet是按序创建pod的,并且前一个pod创建成功(Running或Ready)才会创建下一个,所以即使上面配置文件指定pod的副本为3,因为第2个副本无法创建成功,所以自然也就没有继续创建第3个副本了
    [root@master ~]# kubectl get pod
    NAME READY STATUS RESTARTS AGE
    web-0 1/1 Running 0 2m1s
    web-1 0/1 Pending 0 117s
    [root@master ~]# kubectl get pv
    NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
    nfs-pv1 1Gi RWO Retain Bound default/www-web-0 nfs 13m
    nfs-pv2 5Gi ROX Retain Available nfs 13m
    nfs-pv3 1Gi RWX Retain Available slow 13m

    # 将配置文件test-pv.yaml中nfs-pv3的storageClassName修改为nfs,accessModes改成ReadWriteOnce后重新运行配置文件
    [root@master ~]# kubectl create -f test-pv.yaml
    persistentvolume/nfs-pv3 created
    [root@master ~]# kubectl get pv
    NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
    nfs-pv1 1Gi RWO Retain Bound default/www-web-0 nfs 25m
    nfs-pv2 5Gi ROX Retain Available nfs 25m
    nfs-pv3 1Gi RWO Retain Bound default/www-web-1 nfs 87s
    # 可以发现又成功启动了一个pod绑定到pvc上
    [root@master ~]# kubectl get pod
    NAME READY STATUS RESTARTS AGE
    web-0 1/1 Running 0 20m
    web-1 1/1 Running 0 20m
    web-2 0/1 Pending 0 69s
    [root@master ~]# kubectl get pvc
    NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
    www-web-0 Bound nfs-pv1 1Gi RWO nfs 22m
    www-web-1 Bound nfs-pv3 1Gi RWO nfs 22m
    www-web-2 Pending nfs 2m38s
    [root@centos7-01 ~]# cd /nfs
    [root@centos7-01 nfs]# echo "aaa" > index.html
    [root@centos7-01 nfs]# chmod 777 index.html
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    web-0 1/1 Running 0 25m 10.244.1.74 node1 <none> <none>
    web-1 1/1 Running 0 25m 10.244.2.65 node2 <none> <none>
    web-2 0/1 Pending 0 5m49s <none> <none> <none> <none>
    [root@master ~]# curl 10.244.1.74
    aaa
    [root@centos7-01 /]# cd nfs3
    [root@centos7-01 nfs3]# echo "bbb" > index.html
    [root@centos7-01 nfs3]# chmod 777 index.html
    [root@master ~]# curl 10.244.2.65
    bbb
    • 关于StatefulSet:
      • 匹配Pod name(网络标识)的模式为:$(statefulset名称)-$(序号),比如上面的示例:web-0、web-1。
      • StatefulSet为每个Pod副本创建了一个DNS域名,这个域名的格式为: $(podname).(headless servername),也就意味着服务间是通过Pod域名来通信而非Pod IP,因为当Pod所在Node发生故障时,Pod会被飘移到其它Node上,Pod IP会发生变化,但是Pod域名不会有变化。
      • StatefulSet使用Headless服务来控制Pod的域名,这个域名的FQDN为:$(servicename).$(namespace).svc.cluster.local,其中,cluster.local指的是集群的域名。
      • 根据volumeClaimTemplates,为每个Pod创建一个pvc,pvc的命名规则匹配模式:(volumeClaimTemplates.name)-(pod_name),比如上面的 volumeMounts.name=www, Podname=web-[0-2],因此创建出来的PVC是www-web-0、www-web-1。
      • 删除Pod不会删除其pvc,手动删除pvc将自动释放pv。
    • Statefulset的启停顺序:
      • 有序部署:部署StatefulSet时,如果有多个Pod副本,它们会被顺序地创建(从0到N-1)并且,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态。
      • 有序删除:当Pod被删除时,它们被终止的顺序是从N-1到0。
      • 有序扩展:当对Pod执行扩展操作时,与部署一样,它前面的Pod必须都处于Running和Ready状态。
    • StatefulSet使用场景:
      • 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现。
      • 稳定的网络标识符,即Pod重新调度后其PodName和HostName不变。
      • 有序部署,有序扩展,基于init containers来实现。
      • 有序收缩。