为了管理存储,Kubernetes提供了Secret用于管理敏感信息,ConfigMap存储配置,Volume、PV、PVC、StorageClass等用来管理存储卷。

3.9.1 Secret

Secret解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者Pod Spec中。
Secret可以以Volume或者环境变量的方式使用。

Secret有三种类型:

  • Service Account:用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中;
  • Opaque:base64编码格式的Secret,用来存储密码、密钥等。
  • kubernetes.io/dockerconfigjson:用来存储私有docker registry的认证信息。

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

  1. echo -n "admin"| base64

secrets.yaml

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  password: MWYyZDFlMmU2N2Rm
  username: YWRtaW4=

创建好Secret之后,有两种方式使用它:

  • 以Volume方式
  • 以环境变量方式

将Secret挂载到Volume中

apiVersion: v1
kind: Pod
metadata:
  labels:
    name: db
  name: db
spec:
  volumes:
  - name: secrets
    secret:
      secretName: mysecret
  containers:
  - image: gcr.io/my_project_id/pg:v1
    name: db
    volumeMounts:
    - name: secrets
      mountPath: "/etc/secrets"
      readOnly: true
    ports:
    - name: cp
      containersPort: 5432
      hostPort: 5432

将Secret导出到环境变量中

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-deployment
spec:
  replicas: 2
  strategy:
     type: RollingUpdate
  template:
     metadata:
       labels:
         app: wordpress
         visualize: "true"
     spec:
       containers:
       - name: "wordpress"
         image: "wordpress"
         ports:
         - containerPort: 80
         env:
         - name: WORDPRESS_DB_USER
           valueFrom:
              secretKeyRef:
                 name: mysecret
                 key: username
         - name: WORDPRESS_DB_PASSWORD
           valueFrom:
              secretKeyRef:
                 name: mysecret
                 key: password

kubernetes.io/dockerconfigjson
可以直接使用kubectl命令来创建用于docker registry认证的secret:

kubectl create secret docker-registry myregistrykey --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL

也可以直接读取~/.docker/config.json的内容来创建:

cat ~/.docker/config.json | base64
cat > myregistry.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
   name: myregistrykey
data:
   .dockerconfigjson: UmVhbGx5IHJlYWxseSByZWVlZWVlZWVlZWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGx5eXl5eXl5eXl5eXl5eXl5eXl5eSBsbGxsbGxsbGxsbGxsbG9vb29vb29vb29vb29vb29vb29vb29vb29vb25ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubmdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2cgYXV0aCBrZXlzCg
type: kubernetes.io/dockerconfigjson
EOF
kubectl create -f myregistry.yaml

在创建Pod时,通过imagePullSecrets来引用创建的myregistrykey:

apiVersion: v1
kind: Pod
metadata:
  name: foo
spec:
  containers:
     - name: foo
       image: janedoe/awesomeapp:v1
  imagePullSecrets:
     - name: myregistrykey

Service Account
Service Account用来访问Kubernetes API, 由Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中。

3.9.2 ConfigMap

ConfigMap可以用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制大对象。

ConfigMap
ConfigMap API资源用来保存key-value pair配置数据,这个数据可以在Pods里使用,或者被用来为像controller一样的系统组件存储配置数据。

ConfigMap更方便的处理不包含敏感信息的字符串。
使用ConfigMap配置来创建Kubernetes Volumes,ConfigMap中的每个data项都会成为一个新文件:

kind: ConfigMap
apiVersion: v1
metadata:
   creationTimestamp: 2020-08-23T15:34:00Z
   name: example-config
   namespace: default
data:
   example.property.1: hello
   example.property.2: world
   example.property.file: |-
     property.1=value-1
     property.2=value-2
     property.3=value-3

data一栏包括了配置数据,ConfigMap可以被用来保存单个属性,也可以用来保存一个配置文件。
配置数据可以通过多种方式在Pods里被使用。
ConfigMaps可以被用来:

  1. 设置环境变量的值
  2. 在容器里设置命令行参数
  3. 在数据卷里面创建Config文件

用户和系统组件都可以在ConfigMap里存储配置数据。

创建ConfigMaps
可使用该命令,用给定值、文件或目录来创建ConfigMap。

kubectl create configmap

使用目录创建
比如我们已经有了一些配置文件,其中包含了我们想要设置的ConfigMap的值:

$ ls docs/user-guide/configmap/kubectl/
game.properties
ui.properties

$ cat docs/user-guide/configmap/kubectl/game.properties
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30

$ cat docs/user-guide/configmap/kubectl/ui.properties
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice

使用下面的命令创建一个包含目录中所有文件的ConfigMap。

kubectl create configmap game-config --from-file=docs/user-guide/configmap/kubectl

—from-file指定在目录下的所有文件都会被用在ConfigMap里面创建一个键值对,键的名字就是文件名,值就是文件的内容。

以yaml格式输出配置:

kubectl get configmaps game-config -o yaml

使用文件创建
只要指定一个文件就可以从单个文件中创建ConfigMap。

kubectl create configmap game-config-2 --from-file=docs/user-guide/configmap/kubectl/game.properties
kubectl get configmaps game-config-2 -o yaml

—from-file可以使用多次。

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

kubectl create configmap special-config --from-literal=special.how=very --from-litral=special.type=charm
kubectl get configmaps special-config -o yaml

Pod中使用ConfigMap
ConfigMap可用来填入环境变量。

apiVerion: v1
kind: ConfigMap
metadata:
   name: special-config
   namespace: default
data:
   special.how: very
   special.type: charm
apiVersion: v1
kind: ConfigMap
metadata:
   name: env-config
   namespace: default
data:
   log_level: INFO

在Pod中使用ConfigMap:

apiVersion: v1
kind: Pod
metadata:
   name: dapi-test-pod
spec:
   containers:
     - name: test-container
       image: gcr.io/google_containers/busybox
       command: [ "/bin/sh","-c","env" ]
       env:
         - name: SPECIAL_LEVEL_KEY
           valueFrom:
              configMapKeyRef:
                name: special-config
                key: special.how
         - name: SPECIAL_TYPE_KEY
           valueFrom:
              configMapKeyRef:
                name: speical-config
                key: special.type
       envFrom:
         - configMapRef:
              name: env-config
   restartPolicy: Never

用ConfigMap设置命令行参数
ConfigMap可以用来设置容器中的命令或参数值。
它使用的是Kubernetes的$(VAR_NAME)替换语法:

apiVersion: v1
kind: ConfigMap
metadata:
   name: special-config
   namespace: default
data:
   special.how: very
   special.type: charm

为了将ConfigMap中的值注入到命令行的参数里面,需要使用环境变量替换语句${VAR_NAME}。

apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: gcr.io/google_containers/busybox
      command: [ "/bin/sh","-c","echo $(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)" ]
      env:
        - name: SPECIAL_LEVEL_KEY
          valueFrom:
             configMapKeyRef:
               name: special-config
               key: special.how
        - name: SPECIAL_TYPE_KEY
          valueFrom:
             configMapKeyRef:
               name: special-config
               key: special.type
  restartPolicy: Never

通过数据卷插件使用ConfigMap
还是这个ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
   name: special-config
   namespace: default
data:
   special.how: very
   special.type: charm

将文件填入数据卷:

apiVersion: v1
kind: Pod
metadata:
   name:
spec:
   containers:
     - name: test-container
       image: gcr.io/google_containers/busybox
       command: [ "/bin/sh","-c","cat /etc/config/special.how" ]
       volumeMounts:
       - name: config-volume
         mountPath: /etc/config
   volumes:
     - name: config-volume
       configMap:
         name: special-config
   restartPolicy: Never

运行这个Pod的输出是very。

也可以在ConfigMap值被映射的数据卷里控制路径。

apiVersion: v1`
kind: Pod
metadata:
   name: dapi-test-pod
spec:
   containers:
     - name: test-container
       image: gcr.io/google_containers/busybox
       command: [ "/bin/sh","-c","cat /etc/config/path/to/special-key" ]
       volumeMounts:
       - name: config-volume
         mountPath: /etc/config
   volumes:
     - name: config-volume
       configMap:
         name: special-config
         items:
         - key: special.how
           path: path/to/special-key
   restartPolicy: Never

运行该Pod的结果为very。

3.9.2.1 ConfigMap热更新

ConfigMap是用来存储配置文件的Kubernetes资源对象,所有的配置内容都存储在etcd中。

更新ConfigMap后,

  • 使用该ConfigMap挂载的Env不会同步更新;
  • 使用该ConfigMap挂载的Volume中的数据需要一段时间才能同步更新。

ENV是在容器启动时注入的,启动之后Kubernetes就不会再改变环境变量的值,且同一个namespace中的Pod的环境变量是不断累加的。
为了更新容器中使用ConfigMap挂载的配置,需要通过滚动更新pod的方式来强制重新挂载ConfigMap。

3.9.3 Volume

Kubernetes中的寿命与封装它的Pod相同。
Kubernetes支持多种类型的卷,Pod可以同时使用任意数量的卷。

卷的核心是目录,可能还包含了一些数据,可以通过pod中的容器来访问。
要使用卷,需要为pod指定为卷(spec.volumes字段)以及将它挂载到容器的位置(spec.containers.volumeMounts字段)。

Pod中的每个容器都必须独立指定每个卷的挂载位置。

卷的类型

  • cephfs
  • emptyDir
  • glusterfs
  • hostPath
  • local
  • nfs

emptyDir
emptyDir最初是空的。
当出于任何原因从节点中删除Pod时,emptyDir中的数据将被永久删除。
注意:容器崩溃不会从节点中移除Pod,因此emptyDir卷中的数据在容器崩溃时是安全的。

emptyDir的用法:

  • 暂存空间,例如用于基于磁盘的合并排序;
  • 用作长时间计算崩溃恢复时的检查点;
  • Web服务器容器提供数据时,保存内容管理器容器提取的文件;
apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}

hostPath
hostPath卷将主机节点的文件系统中的文件或目录挂载到集群中。
hostPath的用法如下:

  • 运行需要访问Docker内部的容器;使用/var/lib/docker的hostPath
  • 在容器中运行cAdvisor;使用/dev/cgroups的hostPath
  • 允许pod指定给定的hostPath是否应该在pod运行之前存在,是否应该创建,以及它应该以什么形式存在。

除了所需的path属性之外,用户还可以为hostPath卷指定type。

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      path: /data
      type: Directory

local
local卷表示挂载的本地存储设备、如磁盘、分区及目录。
local卷可以以持久的方式使用,无需手动将pod调度到节点上。

3.9.4 Persistent Volume(持久化卷)

Persistent Volume(PV)是由管理员设置的存储,它是群集的一部分。
Persistent Volume Claim(PVC)是用户存储的请求。
PVC消耗PV资源。

Pod可以请求特定级别的资源(CPU和内存)。
声明可以请求特定的大小和访问模式(例如:可以读/写一次或 只读多次模式挂载。)

卷和声明的生命周期
PV属于集群中的资源。PVC是对这些资源的请求,也作为对资源的请求的检查。
PV和PVC遵循这样的生命周期:

配置(Provision)
有两种方式配置PV:静态和动态。

静态
集群管理员创建一些PV。它们带有可供集群用户使用的实际存储的细节。它们存在于Kubernetes API中,可用于消费。

动态
根据StorageClasses,当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,集群可能会尝试动态的为PVC创建卷。

绑定
在动态配置的情况下,用户创建或已经创建了具有特定存储量的 PersistentVolumeClaim 以及某些访问模式。master 中的控制环路监视新的 PVC,寻找匹配的 PV(如果可能),并将它们绑定在一起。如果为新的 PVC 动态调配 PV,则该环路将始终将该 PV 绑定到 PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。一旦 PV 和 PVC 绑定后,PersistentVolumeClaim 绑定是排他性的,不管它们是如何绑定的。 PVC 跟 PV 绑定是一对一的映射。
如果没有匹配的卷,声明将无限期地保持未绑定状态。随着匹配卷的可用,声明将被绑定。例如,配置了许多 50Gi PV的集群将不会匹配请求 100Gi 的PVC。将100Gi PV 添加到群集时,可以绑定 PVC。

使用
Pod使用声明作为卷。集群检查声明以查找绑定的卷并为集群挂载该卷。
对于支持多种访问模式的卷,用户指定在使用声明作为容器中的卷时所需的模式。

用户进行了声明,并且该声明是绑定的,则只要用户需要,绑定的PV就属于该用户。
用户通过在Pod的volume配置中包含PersistentVolumeClaim来调度Pod并访问用户声明的PV。

持久化卷声明的保护
PVC保护的目的是确保由Pod正在使用的PVC不会从系统中移除,因为如果被移除的话可能导致数据丢失。

注意:当pod的状态为Pending并且Pod已经分配给节点或pod为Running状态时,PVC处于活动状态。

当启用PVC保护alpha功能时,如果用户删除了一个Pod正在使用的PVC,则该PVC不会被立即删除。
PVC删除将被推迟,直到PVC不再被任何Pod使用。

回收
用户用完volume后,可以从允许回收资源的API中删除PVC对象。
PersistentVolumeClaim的回收策略告诉集群在存储卷声明释放后应如何处理该卷。

目前,volume的处理策略有保留、回收或删除。

扩展持久化卷声明
管理员可以通过将ExpandPersistentVolumes特性门设置为true来允许扩展持久卷声明。
管理员还应该启用PersistentVolumeClaimResize 准入控制插件来执行对可调整大小的卷的其他验证。

一旦PersistentVolumeClaimResize 准入插件已打开,将只允许其allowVolumeExpansion字段设置为true的存储类进行大小调整。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
   name: gluster-vo1-default
provisioner: kubernetes.io/glusterfs
parameters:
   result1: "http://192.168.10.100:8080"
   restuser: ""
   secretNamespace: ""
   secretName: ""
allowVolumeExpansion: true

对于扩展包含文件系统的卷,只有在ReadWrite模式下使用PersistentVolumeClaim启动新的Pod时,才会执行文件系统调整大小。
如果正在扩展的卷在pod或部署中使用,则需要删除并重新创建要进行文件系统调整大小的Pod。
文件系统大小调整仅适用于如下文件系统:

  • XFS
  • Ext3、Ext4

持久化卷
每个PV配置中都包含一个spec规格字段和一个Status卷状态字段。

apiVerison: v1
kind: PersistentVolume
metadata:
  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

卷模式
volumeMode的有效值可以是Filesystem或者Block。
如果未指定,volumeMode默认为Filesystem。

访问模式
存储模式包括:

  • ReadWriteOnce ——该卷可以被单个节点以读/写模式挂载
  • ReadOnlyMany ——该卷可以被多个节点以只读模式挂载
  • ReadWriteMany ——该卷可以被多个节点以读/写模式挂载

在命令行中,访问模式缩写为:

  • RWO—-ReadWriteOnce
  • ROX—-ReadOnlyMany
  • RWX—-ReadWriteMany

注意:一个卷一次只能使用一种访问模式挂载,即使它支持多种访问模式。

回收策略
当前的回收策略包括:

  • Retain(保留)——手动回收
  • Recycle(回收)——基本擦除(rm -rf /thevolume/*
  • Delete(删除)——关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷)将被删除

当前,只有 NFS 和 HostPath 支持回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略。

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

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

命令行会显示绑定到 PV 的 PVC 的名称。

PersistentVolumeClaim
每个PVC中都包含一个spec规格字段和一个status声明状态字段。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 8Gi
  storageClassName: slow
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - {key: environment,operator: In,values: [dev]}

选择器
声明可以指定一个标签选择器来进一步过滤该组卷。只有标签与选择器匹配的卷可以绑定到声明。
选择器有2个字段组成:

  • matchLabels: volume必须有具有该值的标签
  • matchExpressions: 这是一个要求列表,通过指定关键字,值列表以及与关键字和值相关的运算符组成。有效的运算符包括In、NotIn、Exists和DoesNotExist。

声明作为卷
通过将声明用作卷来访问存储。
声明必须与使用声明的pod存在于相同的命名空间中。
集群在Pod的命名空间中查找声明,并使用它来获取支持声明的PersistentVolume。该卷然后被挂载到主机的Pod上。

kind: Pod
apiVersion: v1
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: dockerfile/nginx
      volumeMounts:
      - mountPath: "/var/www/html"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim

3.9.5 StorageClass

StorageClass为管理员提供了描述存储“Class(类)”的方法。
不同的class可能会映射到不同的服务质量等级或备份策略。

StorageClass资源
StorageClass中包含provisioner、parameters和reclaimPolicy字段,当class需要动态分配PersistentVolume时会使用到。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
   name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
   type: gp2
reclaimPolicy: Retain
mountOptions:
   - debug

Provisioner(存储分配器)
StorageClass有一个分配器,用来决定使用哪个卷插件分配PV。该字段必须指定。