Kubernetes中的配置与储存

K8S的储存与配置

  • 简单储存:Volume(EmptyDir、HostPath、NFS)

  • 高级储存:PV、PVC

  • 配置储存:ConfigMqp、Secret

配置储存

ConfigMap

一般用于去存储 Pod 中应用所需的一些配置信息,或者环境变量,将配置于 Pod 分开,避免应为修改配置导致还需要重新构建 镜像与容器。

新建一个配置文件redis.properties

redis.host=127.0.0.1
redis.port=6379
redis.password=123456
# 创建一个名为 redis-config 的 ConfigMap
[root@master /]# kubectl create configmap redis-config --from-file=redis.properties

查看该Config的配置信息

[root@master k8s]# kubectl describe cm redis-config
Name:         redis-config
Namespace:    default
Labels:       <none>
Annotations:  <none>
​
Data
====
info:
----
redis.host=127.0.0.1
redis.port=6379
redis.password=123456
​
Events:  <none>

当然你也可以使用资源清单的方式进行创建

  • 创建ConfigMap的资源清单:configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config
data:
  info: |
    redis.host=127.0.0.1
    redis.port=6379
    redis.password=123456
# 创建一个名为 redis-config 的 ConfigMap
[root@master /]# kubectl create -f configmap.yaml

Pod以Volume挂载的方式访问

创建一个Pod,获取刚刚配置在CongfigMap的配置数据:pod-config-map.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-config-map
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    volumeMounts:  # 目录挂载
    - name: config # 将一个名为 config 的ConfigMap挂载到容器内 /configmap/config目录下
      mountPath: /configmap/config
  volumes: # 定义了一个Volume指定类型为ConfigMap并指定我们刚刚创建的名为 redis-config , 对外暴露名字为 config
  - name: config
    configMap:
      name: redis-config
[root@master k8s]# kubectl apply -f pod-config-map.yaml
​
# 进入我们刚刚部署的这个Pod内部
[root@master k8s]# kubectl exec -it pod-config-map /bin/bash
​
# 进入与 redis-config 这个 ConfigMap 挂载的目录
root@pod-config-map:/# cd configmap/config/
​
# ConfigMap 资源清单中data键下面的 info
root@pod-config-map:/configmap/config# ls   
info
​
# 数据以及挂载进来了
root@pod-config-map:/configmap/config# more info 
redis.host=127.0.0.1
redis.port=6379
redis.password=123456

Pod以环境变量的方式访问

  • 定义ConfigMap资源清单文件 :config-pod-env.yaml

    • 如果值是数字的话,需要用双引号引起来

apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config-env
data:
  info:
    redis.host: "127.0.0.1"
    redis.port: "6379"
    redis.password: "123456"
  • 定义Pod的资源清单文件:pod-config-map-env.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-config-map
spec:
  containers:
    - name: nginx
      image: nginx:1.17.1
      env:
        - name: HOST
          valueFrom:
            configMapKeyRef:
              name: redis-config-env
              key: redis.host
        - name: POST
          valueFrom:
            configMapKeyRef:
              name: redis-config-env
              key: redis.port
        - name: PASSWORD
          valueFrom:
            configMapKeyRef:
              name: redis-config-env
              key: redis.password
  • 下面我们开始部署后看看效果

[root@master k8s]# kubectl apply -f config-pod-env.yaml
configmap/redis-config-env created
[root@master k8s]# kubectl apply -f pod-config-map-env.yaml
pod/pod-config-map-env created
​
[root@master k8s]# kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
pod-config-map-env              1/1     Running   0          5s
​
# 进入容器内部 
[root@master k8s]# kubectl exec -it pod-config-map-env /bin/bash
​
# 打印一下环境变量, 这儿port端口没有打印出来, 是因为上面资源清单文件把PORT 写成了 POST  , 这里应该使用 ${POST}即可获取
root@pod-config-map-env:/# echo ${HOST} ${PORT} ${PASSWORD}
127.0.0.1 123456
​
root@pod-config-map-env:/# echo ${POST}
6379

Secret

相对比CongfigMap这种明文储存的机制,K8S配套的也出了一个密文的配置机制,那就是 Secret

  • 其实这种加密也很鸡肋,无非就是使用base64对目标数据进行编码

  • 用法和ConfigMap类似,都是在 Pod 中使用数据卷挂载或者环境变量挂载的方式进行获取

定义secret

  • 定义之前先要将我们的配置数据做base64编码处理

    • echo -n 'admin' | base64 得到admin的Base64编码为 : YWRtaW4=

    • echo -n 'testPassword' | base64 得到admin的Base64编码为 : dGVzdFBhc3N3b3Jk

apiVersion: v1
kind: Secret
metadata:
  name: account-secret
type: Opaque
data:
  username: YWRtaW4=
  password: dGVzdFBhc3N3b3Jk
  • 定义Pod资源清单文件

apiVersion: v1
kind: Pod
metadata:
  name: pod-secret
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    volumeMounts:  # 目录挂载
    - name: account-secret-config # 将一个名为 account-secret-config 的 secret 挂载到容器内 /configmap/config目录下
      mountPath: /configmap/config
  volumes: # 定义了一个Volume指定类型为secret并指定我们刚刚创建的名为 account-secret , 对外暴露名字为 account-secret-config
  - name: account-secret-config
    secret:
      secretName: account-secret

然后部署该资源,进入pod内部查看 /configmap/config的数据,就能获取到我们配置的数据,这里的数据是解密后的

至于通过环境变量的方式进行挂载,和ConfigMap基本就差不多了,在这里就不做更多演示了

subPath的使用

在 ConfigMap 或者 Secret 目录挂载的时候,会将容器中源目录给覆盖掉,如果只想覆盖目录中的某一个文件,可以使用 SubPath

  • 配置方式

    • 定义 volumes 时需要增加 items 属性,配置 key 和 path,且 path 的值不能从 / 开始

    • 在容器内的 volumeMounts 中增加 subPath 属性,该值与 volumes 中 items.path 的值相同

volumeMounts:
- mountPath: /etc/nginx/nginx.conf # 挂载到哪里
  name: configMapOrSecretName
  subPath: etc/nginx/nginx.conf # 与 volumes.[0].items.path 相同
    
volumes:
- configMap:
  name: configMapOrSecretName
  items: # subPath 配置
    key: nginx.conf # configMap 中的文件名
    path: etc/nginx/nginx.conf # subPath 路径

配置的热更新

我们通常会将项目的配置文件作为 configmap 或者 Secret 然后挂载到 pod,如果更新配置文件,是否会自动更新也是分情况的

  • 默认方式:会自动更新

  • subPath : 使用subPath的方式是不会自动更新的

  • 变量方式:如果 pod 中的一个变量是从 configmap 或 secret 中得到,同样也是不会更新的

对于 subPath 的方式,我们可以取消 subPath 的使用,将配置文件挂载到一个不存在的目录,避免目录的覆盖,然后再利用软连接的形式,将该文件链接到目标位置 ,但是如果目标位置原本就有文件,可能无法创建软链接,此时可以基于前面讲过的 postStart 操作执行删除命令,将默认的吻技安删除即可

配置更新的方式也就两种

  • 通过 edit 命令,直接修改配置数据

  • 由于 configmap 我们创建通常都是基于文件创建,并不会编写 yaml 配置文件,因此修改时我们也是直接修改配置文件,而 replace 是没有 --from-file 参数的,因此无法实现基于源配置文件的替换,此时我们可以利用下方的命令实现,该命令的重点在于 --dry-run 参数,该参数的意思打印 yaml 文件,但不会将该文件发送给 apiserver,再结合 -oyaml 输出 yaml 文件就可以得到一个配置好但是没有发给 apiserver 的文件,然后再结合 replace 监听控制台输出得到 yaml 数据即可实现替换

    • kubectl create cm --from-file=nginx.conf --dry-run -oyaml | kubectl replace -f-

不可变的Secret和ConfigMap

对于一些敏感服务的配置文件,在线上有时是不允许修改的,此时在配置时可以设置 immutable: true 来禁止修改

储存

Volumes

Volume 是 Pod 中能够被多个容器访问的共享目录,Volume 与 Pod 的生命周期相同

  • Kubernetes 支持多种类型的 Volume:EmptyDir、HostPath、nfs

EmptyDir

正如其名字所示,一个 emptyDir Volume 是 Host 上的一个空目录 , 创建于pod被调度到某个宿主机上的时候,

演示说明:

  • 通过下面这份配置文件可以发现:我们定义了两个pod和一个名为shared-volume的emptyDir类型的数据卷轴

  • 第一个pod通过volumeMounts挂载了一个名为shared-volumn的数据卷轴

    • 将其挂载在/prodecer_dir目录下

    • 然后在其下创建hello文件,并写入数据 "hello world"

  • 第二个pod通过volumeMounts挂载了一个名为shared-volumn的数据卷轴

    • 将其挂载在/consumer_dir目录下

    • 然后在其查看hello文件,得到"hello world"数据

  • 两个容器通过emptyDir类型的Volume实现数据共享

img

HostPath

HostPath 属性的 volume 使得对应的容器能够访问当前宿主机上的指定目录 ,此时该目录会变成持久化存储目录

一旦这个 pod 离开了这个宿主机 , 但数据也不会随 pod 迁移到其他宿主机上,实际上增加了 Pod 与节点的耦合

演示说明:

  • 我们创建了一个名为persistent-storage的数据卷轴,卷轴类型是hostPath,并指定为宿主机的/mydata目录

  • 上面的Nginx容器挂载改了数据卷轴,并与容器内/usr/share/nginx/html这个目录做了映射

  • 下面我们就可以在不修改Nginx配置文件的情况下,往宿主机的/mydata目录下存放静态文件,可通过该Nginx Pod 进行访问

img

NFS挂载

  • NFS即网络文件系统,其允许一个系统在网络上与它人共享目录和文件

  • NFS相信大家都知道是什么玩意了,比如win10自带功能,局域网通过NFS实现文件共享

  • k8s集群当中挂在nfs文件系统道理也是相通

  • 选一台k8s集群外的同局域网的服务器 ,可相互ping通即可,下载nfs-utils

    • yum install nfs-utils

  • 集群内所有的node节点全部下载:nfs-utils

    • yum install nfs-utils

  • 文件系统服务器设置NFS的挂载路径以及权限等

    • vim /etc/exports

# /mydata:对外暴露的目录 , *:可访问的IP,*为均能访问 , rw:可读可写 ,  no_root-squash:登陆即有root权限,可选:root_squash , 登陆使用匿名者权限
/mydata *(rw,no_root_squash)
  • 文件系统服务启动

    • systemctl start nfs

K8S集群部署应用使用NFS网络储存系统

  • 下面这个资源清单文件表示:Nginx的静态资源目录html与192.168.217.150的/mydata目录做挂载映射

img

PV与PVC

PV的定义

  • 持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备, 或者使用存储类(Storage Class)来动态制备。

  • 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷挂载来实现的。

  • 只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。

  • PV的状态:

    • Available : 空闲,未被绑定

    • Bound : 已经被PVC绑定

    • Released : PVC被删除后,资源已回收,但是PV尚未被重新使用

    • Failed : 自动回收失败

  • 资源清单文件样列:

    • 下面我们基于NFS定义PV的资源清单

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
spec:
  capacity:
    storage: 5Gi         # pv 的容量
  accessModes:           # 访问模式:ReadWriteOnce、ReadWriteMany、ReadOnlyMany
    - ReadWriteOnce      # 访问默认依次为:单节点读写、多节点写、多节点读
  persistentVolumeReclaimPolicy: Recycle  # 回收策略:Retain(保留)、Recycle(回收)、Delete(删除)
  storageClassName: slow                  # 创建 PV 的存储类名,需要与 pvc 的相同
  nfs:             # 连接到 nfs
    path: /mydata                 # 存储路径
    server: 192.168.217.150       # nfs 服务地址

PVC的定义

  • 持久卷申领(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求以及申领。

  • PVC 申领可以请求特定的大小和访问模式 (比如要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载,参见访问模式

  • 集群中已经有的 PV 无法满足 PVC 的需求,PVC 可以通过 StorageClass 实现自动创建一个 PV

  •  

  • 资源清单文件样列:

    • 他们通过下面这个资源清单文件就能找到上面制备的 PV

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteOnce        # 权限需要与对应的 pv 相同
  resources:
    requests:
      storage: 5Gi       # 资源可以小于 pv 的,但是不能大于,如果大于就会匹配不到 pv
  storageClassName: slow # 名字需要与对应的 pv 相同
#  selector: # 使用选择器选择对应的 pv
#    matchLabels:
#      release: "stable"
#    matchExpressions:
#      - {key: environment, operator: In, values: [dev]}
  • 当用户创建一个 PVC 对象后,主节点会监测新的 PVC 对象,并且寻找与之匹配的 PV 卷,找到 PV 卷后将二者绑定在一起。

  • 如果找不到对应的 PV,则需要看 PVC 是否设置 StorageClass 来决定是否动态创建 PV

    • 若没有配置,PVC 就会一致处于未绑定状态,直到有与之匹配的 PV 后才会申领绑定关系。

    • 若设置了 StorageClass ,则会动态创建 PV,与之绑定

Pod与PVC与PV

  • Pod 将 PVC 当作存储卷来使用,集群会通过 PVC 找到绑定的 PV,并为 Pod 挂载该卷

  • Pod 一旦使用 PVC 绑定 PV 后,为了保护数据,避免数据丢失问题,PV 对象会受到保护,在系统中无法被删除

  • 当用户不再使用其存储卷时,他们可以从 API 中将 PVC 对象删除, 从而允许该资源被回收再利用。

  • PersistentVolume 对象的回收策略告诉集群, 当其被从申领中释放时如何处理该数据卷。 目前,数据卷可以被保留、回收或删除

    • 保留 (Retain) :

      • 处于保留状态的PV持久卷仍然存在且不能被其他PVC申领 , 可通过下面的手段进行手动回收

        1. 删除PV对象 , 但PV对象位于外部的储存资产继续存在 , 然后根据情况是否手动删除所关联的储存资产

        2. 删除PV对象 , 可以基于存储资产的定义创建新的 PV 卷对象 , 达到重用储存资产的目的

    • 回收 (Recycle):

      • 目前已被废弃 , 官网建议使用动态制备

    • 删除 (Delete):

      • 从看k8s中移除改卷,且会移除所有的储存资产

      • 动态制备的卷默认就是采用的这种回收策略

资源清单文件样列:

  • 首先定义一个Volume , 规则为PVC(persistentVolumeClaim) , 然后给定PVC的名称

  • 然后再以卷轴挂载的方式,挂载到某个容器下,容器下某个目录映射

#在 pod 的挂载容器配置中,增加 pvc 挂载
containers:
  ......
  volumeMounts:
    - mountPath: /tmp/pvc
      name: nfs-pvc-test
volumes:
  - name: nfs-pvc-test
    persistentVolumeClaim:
      claimName: nfs-pvc    # pvc 的名称

StorageClass

  • 上面的案列中,我们手动的制备了PV , 然后定义PVC时进行关联选择,最后PVC再与Pod进行挂载

  • 但我们也说到了一个词语 StorageClass ,用于PVC自动创建 PV , 这样就不用我们在手动制备PV了

  • 也就是说我们只需要定义PVC就行,然后PVC通过StorageClass来自动创建符合需求的PV

  • 每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV。

下面我们来看这么一份资源清单文件

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  namespace: kube-system
  labels:
    app: nfs-client-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: 192.168.217.150
            - name: NFS_PATH
              value: /mydata
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.217.150
            path: /mydata

我们在创建一个StorageClass的资源清单

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
  namespace: kube-system
provisioner: fuseim.pri/ifs  # 外部制备器提供者,编写为提供者的名称
parameters:
  archiveOnDelete: "false"    # 是否存档,false 表示不存档,会删除 oldPath 下面的数据,true 表示存档,会重命名路径
reclaimPolicy: Retain         # 回收策略,默认为 Delete 可以配置为 Retain
volumeBindingMode: Immediate  # 默认为 Immediate,表示创建 PVC 立即进行绑定,只有 azuredisk 和 AWSelasticblockstore 支持其他值

RBAC配置

  • 下面这段代码或许很多人看不懂,大致就是分配账号与角色,属于权限管理的一块,K8S也是内置了RBAC的权限模块

  • 上面的Deployment定义中 , 我们使用的账户是:serviceAccountName: nfs-client-provisioner

  • 下面的代码就是为:nfs-client-provisioner 这个账户进行权限授权

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
  namespace: kube-system
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
  namespace: kube-system
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: kube-system
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: kube-system
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

PVC处于pending状态:

  • 在K8S版本1.20z之后,移除了一个对于SelfLink的支持,而默认上面指定的 provisioner 版本需要 SelfLink 功能,因此 PVC 无法进行自动制备。

  • 配置 SelfLink,修改 Api Server 配置文件:vim /etc/kubernetes/manifests/kube-apiserver.yaml

spec:
  containers:
  - command:
    - kube-apiserver
    - --feature-gates=RemoveSelfLink=false # 新增该行
    ......
  • 修改后更新部署:kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml

或者将StorageClass的资源清单文件中的 provisioner 置为:任选一

  • gcr.io/k8s-staging-sig-storage/nfs-subdir-external-provisioner:v4.0.0

  • registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0

然后我们就可以开始定义PVC了,并在PVC中通过 storageClassName 指定我们自己创建的 storageClass

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: auto-pv-test-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 300Mi
  storageClassName: managed-nfs-storage

  

 
posted @ 2021-01-31 23:49  鞋破露脚尖儿  阅读(1042)  评论(0编辑  收藏  举报