k8s持久化存储方案

社区K8s运维

0.前言

存储对于系统而言,一直都是保证数据安全的基础,重要性不言而喻,通常我们都希望存储拥有稳定,可用,高性能,高可靠的特点。从存储本身来说,存储介质一般是机械硬盘或者固态硬盘,对于存储性能要求较高的服务,推荐采用固态硬盘。存储种类一般为本地存储(DAS)和网络存储(NAS),本地存储通常直接接在服务器上即插即用,网络存储则采用协议网关的方式对外提供存储服务。对于用户而言,我们希望存储就是一个盘,可以用来存储数据就好。
k8s集群中用来处理存储的资源对象通常有:
(1)configMap : 在k8s中专门用来存储配置文件;
(2)Secret : 有一些需要加密的信息,例如密钥、用户名密码信息在Secret中可以被加密,是k8s中加密的解决方案【base64】;
(3)Volume : 用于赋予k8s中pod共享存储卷的能力,例如可以通过nfs共享,本地磁盘目录共享等等;
(4)Persistent Volume : 简称PV【持久卷】,还包含一个PVC,通过服务进行持久卷的构建;
(5)StorageClass : 存储类可以动态的绑定PV(持久卷)和创建PVC(持久卷要求);
(6)Nfs / Cephfs : 常用的分布式共享存储解决方案。
接下来,我们来看下这些资源对象在k8s中都是如何使用的。

1.ConfigMap描述

在部署应用的时候,通常需要给应用提供一些配置信息,比如database的IP、端口、用户名和密码等,在k8s里,我们可以通过ConfigMap实现。

1.1 创建ConfigMap

(1)基于目录创建

cat /data/config-map/dir/demo1.pro
name=zhangsan
age=18
kubectl create configmap dir-config --from-file=/data/config-map/dir

如果有多个文件的话,可以读取多个文件中配置项。

(2)基于文件创建

cat /data/config-map/demo2.pro
name=lisi
age=20
kubectl create configmap file-config --from-file=/data/config-map/demo2.pro

(3)通过字面值创建

kubectl create configmap text-config --from-literal=name=wangwu --from-literal=age=18

通过字面值创建的ConfigMap不便于记录和管理,通常不推荐使用。

(4)基于声明文件创建

cat /data/config-map/demo-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: configmap-demo
  namespace: default
data:
  name: lisa
  age: "18"
cd /data/config-map/
kubectl apply -f demo-cm.yaml

1.2 查看ConfigMap

(1)查看所有

kubectl get cm

picture.image (2)查看详情
查看某个configmap的详情:

kubectl describe cm configmap-demo
Name:         configmap-demo
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
age:
----
18
name:
----
lisa

BinaryData
====

(3)以yaml文件形式查看

kubectl get cm configmap-demo -o yaml
apiVersion: v1
data:
  age: "18"
  name: lisa
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"age":"18","name":"lisa"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"configmap-demo","namespace":"default"}}
  creationTimestamp: "2024-09-25T05:48:55Z"
  name: configmap-demo
  namespace: default
  resourceVersion: "101986"
  uid: 4f592324-ec35-4c1f-a951-18ea10bbc093

可以看到configmap的详情以声明式文件的形式展示了出来。

1.3 pod使用ConfigMap

cd /data/config-map
cat config-map-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: configmap-env-demo
spec:
  containers:
    - name: configmap-env-demo
      image: harbor.weiyigeek.top/test/busbox:latest
      imagePullPolicy: IfNotPresent
      command: [ "/bin/sh", "-c", "env; echo Name: ${USERNAME_KEY}, Site: ${SITE_KEY}" ]
  env: # 关键点
    - name: USERNAME_KEY
      valueFrom:
        configMapKeyRef: # 关键点key来源设置
          name: configmap-demo
          key: special.name # Value
    - name: SITE_KEY
      valueFrom:
        configMapKeyRef:
          name: configmap-demo
          key: special.site # Value
  envFrom: # 关键点
    - configMapRef:
        name: configmap-demo # Key
        restartPolicy: Never

注意:上面的声明式文件中,env配合valueFrom是指定configmap中部分参数到pod的环境变量中,而envFrom则会将configmap中所有参数都引入到pod的环境变量中。 看下结果:

kubectl logs -f configmap-pod-demo
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
HOSTNAME=configmap-pod-demo
SHLVL=1
HOME=/root
NAME=lisa // env引入的环境变量
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
age=18 // envFrom引入的环境变量
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NGINX_SERVICE_PORT_8080_TCP_ADDR=10.96.208.179
NGINX_SERVICE_SERVICE_HOST=10.96.208.179
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
NGINX_SERVICE_PORT_8080_TCP_PORT=8080
NGINX_SERVICE_PORT_8080_TCP_PROTO=tcp
NGINX_SERVICE_PORT=tcp://10.96.208.179:8080
NGINX_SERVICE_SERVICE_PORT=8080
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
name=lisa // envFrom引入的环境变量
NGINX_SERVICE_PORT_8080_TCP=tcp://10.96.208.179:8080
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/
AGE=18 // env引入的环境变量
Name: lisa, Age: 18 // 打印的信息

1.4 deployment中使用ConfigMap

将ConfigMap file-config挂载到deploy的pod里面

cd /data/config-map
cat config-map-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: configmap-deploy-demo
spec:
  replicas: 1
  selector: # 注意它是在Deployment控制器中需要依赖的
    matchLabels:
      app: configmap-deploy-demo # 匹配的Pod标签非常重要
  template:
    metadata:
      labels:
        app: configmap-deploy-demo
    spec:
      containers:
      - name: configmap-deploy-demo
        image: docker.m.daocloud.io/library/nginx
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: config-volume
          mountPath: /usr/share/nginx/html/
      volumes:
      - name: config-volume
        configMap:
          name: file-config
kubectl apply -f config-map-deploy.yaml

查看下deployment管理的pod

kubectl get pod -o wide |grep configmap-deploy

picture.image

向pod发起请求看下结果:

curl http://172.30.169.137/demo2.pro

picture.image 可以

1.5 删除ConfigMap

kubectl delete cm configmap-demo

2. Secret描述

可以看到ConfigMap是以明文的形式保存数据的,显然对于密码和秘钥文件,就不太适合用ConfigMap存储,在k8s中通常用Secret处理该类文件。 Secret总共有三种类型:
(1)Service Account:由 Kubernetes 自动创建用来访问Kubernetes API,并且会自动挂载到Pod的 /run/secrets/kubernetes.io/serviceaccount 目录中;
(2)Opaque:Base64编码格式的Secret 用来存储密码、密钥等,注意加密程度并不高;
(3)Kubernetes.io/dockerconfigjson : 将私有仓库的登陆认证信息进行存储。

2.1 Service Account

查看之前创建的pod

kubectl get pod configmap-deploy-demo-8544455845-9vnwx

picture.image 进入到pod里面,查看secret相关文件

kubectl exec -it configmap-deploy-demo-8544455845-9vnwx /bin/bash
ls /run/secrets/kubernetes.io/serviceaccount/

picture.image 可以看到ca.crt,namespace,token三个文件,是有k8s创建的鉴权认证相关文件。

2.2 Opaque Secret描述

Opaque Secret 类型的数据是一个map类型,要求Value是Base64编码格式

2.2.1 创建secret

(1)先创建两个base64加密的字符串:

echo -n "zhangsan" | base64
emhhbmdzYW4=
echo -n "12345678" | base64
MTIzNDU2Nzg=

(2)创建声明文件

cd /data/secret
cat opa-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: secret-test
  type: Opaque
data:
  name: emhhbmdzYW4=
  pass: MTIzNDU2Nzg=

(3)创建opaque secret

kubectl apply -f opa-secret.yaml
2.2.2 查看secret

(1)获取所有secret

kubectl get secret

(2)查看secret详情

kubectl describe secret secret-test
2.2.3 环境变量使用secret

环境变量的形式使用secret,声明文件如下:

cd /data/secret
cat opa-secret-env.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secret-test-env # 元数据名称
spec:
  replicas: 2
  selector: # 注意它是在Deployment控制器中需要依赖的
    matchLabels:
    app: secret-test-env # 匹配的Pod标签非常重要
  template:
    metadata:
      labels:
        app: secret-test-env
    spec:
      containers:
      - name: secret-test-env
        image: harbor.weiyigeek.top/test/busbox:latest
        imagePullPolicy: IfNotPresent
        command: [ "/bin/sh", "-c", "env; echo Name: ${USERNAME}, Pass: ${PASSWORD}; sleep 700" ]
      env: # 环境变量设置
      - name: USERNAME
        valueFrom: # Value 来源
          secretKeyRef: # 关键点:上面我们接触过 configMapKeyRef ,此处是 secretKeyRef
            name: secret-test
            key: name

      - name: PASSWORD
        valueFrom:
          secretKeyRef:
            name: secret-test
            key: pass

创建deployment

kubectl apply -f opa-secret-env.yaml

查看pod信息

kubectl get pod |grep secret

picture.image

查看Pod的日志信息

kubectl logs -f secret-test-env-75d6689bdf-8z26t
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
HOSTNAME=secret-test-env-75d6689bdf-8z26t
SHLVL=1
HOME=/root
USERNAME=zhangsan // secret中定义的name
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NGINX_SERVICE_SERVICE_HOST=10.96.208.179
NGINX_SERVICE_PORT_8080_TCP_ADDR=10.96.208.179
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
NGINX_SERVICE_PORT_8080_TCP_PORT=8080
NGINX_SERVICE_PORT_8080_TCP_PROTO=tcp
NGINX_SERVICE_SERVICE_PORT=8080
NGINX_SERVICE_PORT=tcp://10.96.208.179:8080
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
NGINX_SERVICE_PORT_8080_TCP=tcp://10.96.208.179:8080
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/
PASSWORD=12345678 // secret中定义的password
Name: zhangsan, Pass: 12345678 // 容器输出信息
2.2.4 文件挂载的形式

以挂载文件的形式使用secret,声明文件如下:

cd /data/secret
cat opa-secret-volume.yaml
apiVersion: v1 # 注意点: Pod 是 v1
kind: Pod
metadata:
  name: secret-test-volume
  labels:
    name: secret-test-volume
spec:
  volumes:
  - name: secrets # 关键点
    secret:
      secretName: secret-test
  containers:
  - name: seret-test-1
    image: docker.m.daocloud.io/library/busybox
    command: [ "/bin/sh", "-c", "ls /etc/secrets; sleep 700" ]
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: secrets
      mountPath: "/etc/secrets" # 关键点挂载到此目录之中
      readOnly: true

创建pod

kubectl apply -f opa-secret-volume.yaml

查看pod信息

kubectl get pod  |grep volume

picture.image 查看挂载目录

kubectl exec secret-test-volume -- ls /etc/secrets

picture.image 文件里面的内容,name是zhangsan,pass是12345678。

2.3 kubernetes.io/dockerconfigjson 描述

dockerconfigjson可以让没有docker login的node也能拉取私有镜像仓库里面的镜像,具体操作如下。
(1)创建secret

kuberctl create secret docker-registry private-registry-key \
--docker-server=192.168.159.163:8080 \
--docker-username=admin \
--docker-password=Harbor123456789 \
--docker-email=master@163.com

(2)在声明文件中引用secret

cd /data/secret
cat secret-docker-json.yaml
apiVersion: v1
kind: Pod
metadata:
  name: docker-config-json
spec:
  containers:
  - name: busybox
    image: 192.168.159.163:8080/images/busybox:v1.0 # 拉取的私有镜像
    imagePullSecrets: # 关键点: 通过 imagePullSecrets 来引用刚创建的`   private-registry-key `
    - name: private-registry-key # 创建的 docker-registry 类型 secret 的名称

3. volume描述

在k8s中,文件的生命周期和pod的生命周期一致,pod重启的话,之前pod里面的文件也会跟着消失。如果pod中有一些重要的文件需要持久化的话,就需要用到volume,将pod中的文件保存到指定位置。
在k8s集群中,volume常见的有以下两种种类型:
(1)emptyDir:空卷
(2)hostPath:主机路径卷
(3)nfs:网络存储

3.1 emptyDir案例

(1)创建声明式文件

cd /data/volume
cat empty-dir-test.yaml
apiVersion: v1 # 注意点: Pod 是 v1
kind: Pod
metadata:
  name: emptydir-test-1
  labels:
    name: emptydir-test-1
spec:
  volumes:
  - name: cachedir # 关键点卷名称
    emptyDir: {} # 使用空卷
  containers:
  - name: emptydir-container-1
    image: docker.m.daocloud.io/library/busybox
    command: [ "/bin/sh", "-c","sleep 700" ]
    imagePullPolicy: IfNotPresent
    volumeMounts:
     - name: cachedir # 引用卷配置名称
       mountPath: "/cache/container1" # 挂载空卷到此目录之中
  - name: emptydir-container-2
    image: docker.m.daocloud.io/library/busybox
    command: [ "/bin/sh", "-c","sleep 700" ]
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: cachedir
      mountPath: "/cache/container2" # 挂载空卷到此目录之中
  restartPolicy: Never

(2)创建pod

kubectl apply -f empty-dir-test.yaml

(3)查看pod

kubectl get pod |grep emptydir

(4)创建共享文件 看之前的声明文件知道,我们在pod里面创建了两个container,挂载目录分别是/cache/pod1和/cache/pod2,接下来我们来试验一下。

kubectl exec emptydir-test-1 -c emptydir-container-1 -it -- sh
echo $HOSTNAME-$(date) > /cache/container1/host.log
cat /cache/container1/host.log
emptydir-test-1-Thu Sep 26 07:21:42 UTC 2024
kubectl exec emptydir-test-1 -c emptydir-container-2 -it -- sh
echo $HOSTNAME-$(date) >> /cache/container2/host.log
cat /cache/container2/host.log
emptydir-test-1-Thu Sep 26 07:21:42 UTC 2024
emptydir-test-1-Thu Sep 26 07:22:29 UTC 2024

容器2中host.log文件可以看到容器1中写入的内容,说明两个容器共用了pod的emptyDir存储。

注意:如果我们删掉了pod,则emptyDir也会失效。

3.2 hostPath案例

(1)创建声明文件

cd /data/volume
cat host-path-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hostpath-test-1 # 元数据名称
  spec:
    replicas: 2
    selector: # 注意它是在Deployment控制器中需要依赖的
      matchLabels:
        app: hostpath-test-1 # 匹配的Pod标签非常重要
    template:
      metadata:
        labels:
          app: hostpath-test-1
      spec:
        volumes: # volumes 关键点
        - name: test-volume
          hostPath: # 采用 hostPath 类型的卷
            type: DirectoryOrCreate # 卷类型此处选择如果DirectoryOrCreate如何子节点上没有该目录便会进行创建
          path: /tmp/data/volume # 各主机节点上已存在的目录
        containers:
        - name: hostpath-test
          image: docker.m.daocloud.io/library/busybox
          imagePullPolicy: IfNotPresent
          command: [ "/bin/sh", "-c", "sleep 700" ]
          volumeMounts:
          - name: test-volume # 挂载指定卷目录
            mountPath: /disk/hostpath/

(2)创建deployment

kubectl apply -f host-path-test.yaml

(3)查看pod

kubectl get pod -o wide |grep hostpath

picture.image (4)测试宿主机与pod共享目录
第一个pod:

kubectl exec -it hostpath-test-1-7fd7856d45-m2g8v -- /bin/sh
echo $(date) > /disk/hostpath/test.log

第二个pod:

kubectl exec -it hostpath-test-1-7fd7856d45-nh9zh -- /bin/sh
echo $(date) >> /disk/hostpath/test.log

(5)查看主机文件
在node1上:

cat /tmp/data/volume/test.log
Thu Sep 26 07:38:26 UTC 2024

在node2上:

cat /tmp/data/volume/test.log
Thu Sep 26 07:49:38 UTC 2024

可以看到文件内容同pod容器中文件内容一样,说明容器中文件确实挂载到了宿主机节点指定目录。 注意:当deployment运行多个pod的时候,如果pod运行在不同的node节点,往共享目录写入内容可能会导致宿主机上共享文件内容不一致的情况。

(7)删除deployment

kubectl delete -f volume-test.yaml

检查宿主机发现目录和里面的文件依旧存在。

3.3 使用nfs做存储卷

(1)创建声明式文件

cd /data/volume
cat nfs-volume.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-test-1 # 元数据名称
spec:
  replicas: 1 # 副本数量为1,否则大家都写进同一个目录可能会导致日志记录混乱;
  selector: # 注意它是在Deployment控制器中需要依赖的
    matchLabels:
      app: nfs-test-1 # 匹配的Pod标签非常重要
  template:
    metadata:
      labels:
        app: nfs-test-1
    spec:
      volumes: # volumes 关键点
      - name: test-volume
        nfs: # 采用 nfs 类型的卷
          server: 192.168.159.166 # nfs 主机地址
          path: '/data/nfs' # nfs 共享目录
      containers:
      - name: nfs-test
        image: docker.m.daocloud.io/library/busybox
        imagePullPolicy: IfNotPresent
        command: [ "/bin/sh", "-c", "sleep 700" ]
        volumeMounts:
        - name: test-volume
          mountPath: /app/tools/ # 指定卷挂载到Pod指定的目录路径之中

(2)创建deployment

kubectl apply -f nfs-volume.yaml

(3)查看pod

kubectl get pod |grep nfs

picture.image

(4)进入pod并创建文件

kubectl exec -it nfs-test-1-5c58dc677f-2njrg -- /bin/sh
echo bbb > /app/tools/b.txt

(5)检查nfs共享目录

cat /data/nfs/b.txt
bbb

可以看到nfs共享目录下确实创建了b.txt文件,且内容与pod中文件内容一样。

4.pv和pvc描述

pv全称是PersistentVolume,也即持久化存储卷的意思,也是k8s持久化pod中数据的一种方案,pv独立于pod的生命周期,并且不支持命名空间划分,是管理员设置的存储供pod使用的。pvc则是用户设置的存储请求,是支持命令空间划分的,pvc会消耗pv创建的存储资源,一般会设置pvc请求特定大小和模式的pv,获取存储资源。

4.1 pv和pvc使用

(1)pv声明式文件 nfs类型的pv:

cd /data/volume/pv
cat nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-test
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce # 单节点读写
  persistentVolumeReclaimPolicy: Delete # 回收策略
  storageClassName: slow # 此持久卷所属的StorageClass的名称,空值意味着此卷不属于任何存储类。
  mountOptions:
  - hard
  - nfsvers=4.1
  nfs: #PV 类型
    path: /data/nfs
    server: 192.168.159.166

hostPath类型的pv:

cd /data/volume/pv
cat hostpath-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: hostpath-test
spec:
  capacity:
    storage: 2Gi
  accessModes:
  - ReadWriteOnce # 单节点读写
  hostPath:
    path: /tmp/local

创建以上两个pv

kubectl apply -f nfs-pv.yaml
kubectl apply -f hostpath-pv.yaml

(2)pvc声明式文件 使用nfs类型pv的pvc

cd /data/volume/pv
cat nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-test-claim
spec:
  accessModes:
  - ReadWriteMany
  storageClassName: nfs # 关键点(与上面的NFS创建的PV进行关联)
  resources:
    requests:
      storage: 1Gi 

使用hostPath类型pv的pvc

cd /data/volume/pv
cat hostpath-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: hostpath-test-claim
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 1Gi

(3)pod消费pv声明文件

cd /data/volume/pv
cat nfs-pv-consumer.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pv-consumer
spec:
  containers:
  - name: myfrontend
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 80
      name: web
    volumeMounts: # 卷挂载
    - name: pv-claim # 卷名称
      mountPath: /usr/share/nginx/html # 卷挂载路径
    volumes:
    - name: pv-claim
      persistentVolumeClaim:
        claimName: nfs-test-claim

(4)创建pod

cd /data/volume/pv
kubectl apply -f pod-pvc-test.yaml

(5)查看pod信息

kubectl get pod -o wide|grep consum

picture.image

进入pod检查共享目录

kubectl exec -it pv-consumer -- /bin/bash
ls /usr/share/nginx/html/
a.txt  b.txt

可以看到nfs目录下的文件在这里也可以看到,说明目录挂载成功。

5.总结

常用的k8s存储方案我们上面都已经提到了,在生产环境的话推荐使用基于nfs类型的pv、pvc模式的存储方案,可以更加安全的存储数据,并且做数据迁移和备份也更加的方便。

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论