K8s——数据持久化

数据持久化一直都是需要我们非常关心的问题,docker如此,K8s也不例外。在k8s中,有一个数据卷的概念。

k8s数据卷主要解决了以下两方面问题:

  • 数据持久性:通常情况下,容器运行起来后,写入到其文件系统的文件时暂时性的。当容器崩溃后,kebelet将这个容器kill掉S # M J,然后生成一个新的容器,此时,新运行的容器将没有原来容器内的文件,因为x U e Q 7 I容器是重新从镜像创建的。
  • 数据共享:同一- . | R s O 1个pod中运行的容器之间,经常会存在共享文件/文件夹的需求。

X k Qk8s中,Volume(数据卷)存在明( - $ 6 w 9确的生命周期(与包含该数据卷的容器] Y 3组(pod)相同)。因此Volume的生命周期比同一容器组(pod)中任意容器的生命周期要更长,不管容器重启了多少次,数据都被保留下来。当然,如果pod不存在了,数据卷自然退出了。此时,根据pm g 5 cod所使用的数据卷类型不同,数据可能随着数据卷的退出而删( m & T J除,也可能被真正持久化,并在下次容器组重启时仍然可以使用。

从根本上来说,N S v M H q L M Z一个数据卷仅仅是一个可以被pod访问的目录或文件。这个目录是怎么z 5 f $ [ &来的,取决于该数据卷的类型(不同类型的数据卷使用不同的存储介质)。同一个pod中的两个容器可以将一个数据卷挂载到不同的目录下。

一、数据卷类型

k8s目A h h前支持28种数据7 = T 7 v U E卷类型(其中大多数特定l r 1于云环境),这里将写下在k8s中常用的几种数据卷类型

1、empty3 a ; | # X + IDir

emptyDir类型的数据卷在创建pod时分配给该pod,并且直到pod被移除,该数据卷才被释放。该数据卷初始分配时,始终是一个空目录。同一个pod中的不同容器都可以对该目录执行读写操作,并且共享其中的数据(尽管不同容器可能将该数据卷挂载到容器中的不同路径)。当pod被删除后,emptyDir数据卷中的数据将被p 2 X a c y v 8 0永久删除。(注:容( # 6 9 p ] g i 8器奔溃时,kubelet并不会删除g . spod,而仅仅是将容器重启,因此emptyDir中的数据在容器崩溃并重启后,仍然是存在的)

emptyDir的使用场景如下:

  • 空白的初1 F ; O L 4 1 H v始空间,例如合并/排序算法中,临时将数据保存在磁盘上。
  • 长时间计算中存储检查点(中间结果),以便容器崩溃时,可以从p P i 6 O上一次存储的检查点(中间结果)继续进行,而不是从头& e 4 m开始。
  • 作为两个容器的共享存储,使得第一个内容管理的容器可以将生成的数据存入其中,同时由一个webserver容器对外提供这些页面。
  • 默认情况下,3 } y `emptyDir数据卷存储在node节点的存储介质(机械硬盘、SSD@ Q c z x D c或网络存储)上。

emptyDir使用示例

// Pod的yaml文件@ ^ V r k如下
apiVersion: v1
kind: Pod
metf ! { O % p g @ badata:
na0 s 6 # d W . 4 )me: read-write
spec:
containers:
- name: write             # 定义一个名称为writ 1 3 * ! Te的容器
image: busybox
volumeMounts:
- mountPath: /write               # 当数据持久化类型为emptyDir时,这里的路径指的是容器内A X _的路径
name: share-volume          # 指定本地的目录名
args:            # 容器运行后进3 u  b & D 1 N行写的操作
- /bin/sh
- -c
- echo "% B t P | ~ 0 D ?emtydir test" > /write/hel# g L M G ] Plo; sleep 30000
- name: read           # 定义一个名称为read的容器
im= Z ] j 4 vage: busyboH C Z S G { f N =x
volumeMounts:
- mountPathA 8 K p 0 | K U w: /read
name: share-volume         # 指定本% _ i 5 ~ e 3 r地的目录名
args:              # 容器运行之后进行读操作a  k 0 O 7
- /bin/sh
- -c
- cat /read/hello; sleep 30000
volumes:                       # 这里的volume是指对上面挂 y E , B载的解释
- name: share-volume         # 这里的名称必须和上述mountPath下的name的名称对应
emptyDir: {}) 6 L I                    # 这里表示是个空目录,主要是定义了一个数据持久化的类型
//执行yaml文件
[root@docker-k8s01 ~]# kubectl apply -f emtydir.yaml
//进入第二个容器名为read的容器查看
[root@dock? & ~er-k8s01 ~]# kubectl exec -it read-write -c rea* I R O z ud /bin/sh
/ # cat /read/hello
emtydir test                               # #查看指定挂载的目录下是否和write容器中的内容: ` 1 j一致
//至此,起码可以确认这两个pod是挂载了同一个本地目录,文件内容都一致。
//那么,现在看看具体挂载的是本地哪个目录?
[root@docker-k8s01 ~]# kubectl get pod -o wide
//我这里是运行在l u S y ~ 7 k Q 2node02节点的,所以接下来需要到node02节点上进行查看
[root@dockC u z v y I mer-k8s03 ~]h V r ~# docker ps       # 通过此命令查看出运行的容器ID号
CONTAINER ID        I$ C [ c ~MAGE
27f7bcc70689        busybox
5835bf143694T p  f z Y        busc x & ) J { =ybox
// 查看第一个容器信息
[root@docker-k8s03 ~Y j _ ` a]# docker inspect 27f7bcc70689
"Mounts"O I N # K v #: [                            # 找到mount字段
{
"Type": "bind",
"^ M u I ! ( X QSource": "/var/lib/kubelet/pods/60f6r u F @ o O } w 33940-5f06-4650-a998-M * ; j z864f542ac87f/volumes/kuberneteL M 0 @ = H t Ms.io~empty-dir/share-volume",
# 上面的source就是指定的本地目录
"Destination": ) i z & ]"/read",
"Mode": "",
"RW": true,
"Propo - l #agation": "rprivate"
},
[root@docker-k8s03 ~]# docker inspect 5835bf143694
"Mounts": [
{
"Type": "bind",
"Source": "/var/lib/kubelet/pods/60f63940-5f06-? - [ v P 04650-a998-864f54 | z ) E42ac87f/volumes/kubernetes.io~empty-dir/shareK e $ o ) #-volume",w o l - F ? j _
# 可以看到,上面指定的本地目录和第一个容器指定的是同一个目录
"DG A ; ( r 9 5 bestination": "/write",
"Mode": "",
"RW": true,
"Prop= c 6 = Aag& ^ i 8 iation": "rprivate"
},
// 查看本地该目录下的内容,和pod中的一致
[root@docker-k8s03 ~]# cat /var/lib/kubelet/pods/60f63940-5f06-4650-a998-864f542ac87f/volumes/kuber& I 0 f 3 ?netes.io~empty-dir/share-volume/hello
em` l ( % 7tydir test

至此$ o } l j * h,emptyDir的特性就已经验证了,只要这个pod中还有一个容器在运行,那么这个{ } c : U b g本地的数据就不会丢失,但如果这个pod被删除Y Q q 3 v T =,那么本地的数据也将不复存在。
如下:

//node02上删除一个pod并再次查看本地目录
[root@docker-k8s03 ~]# docker rm -f 27f7bcc70689
27f7bcc70689
/) u S D G ` + . M/ 查看可以b s n V [ j V r X发现还在` ^ 0 Z ; c 4 %
[root@df J yockB h / (er-k8s03 ~]# cat /var/lib/kJ A g S l 9ubelet/pods/60f63940-5f06-4650-a998-864f54| 8 7 G  ,2ac87f/volumes/kubernetes.io~empty-dir/share-voluK O S A - ) 5 {me/hello
emtydir test
//在master上将此pod删除,再次去node01节点上查看本地目录是否存在
[root@docker-k8s01 ~]# kubE + & ~ 5 `ectl delete -f emtydir.yaml
pod "read-write"_ | : } T k 9 B ~ deleted
//可以看到提示不存在此目录
[root@docker-k8s03 ~]# cat /var/lib/kubelet/pods/60f63940-5f06-4650-a998-864f542ac87f// s P ,volumes/kubernetes.io~empty-dirT 9 4 ; u V ~ i 2/share-volume/hello
cat: /var/lib/kubelet/pods/60f63940-5f06-4650-a998-864f542ac87f/volum@ : 6es/kubernetes.io~empty-dir/share-volumo m r 1 _ me/u s e L N Chello: No suc} N ~ Ih file or directory

emptyDir总结
同个pod里面的不同容器,共享同一个持久化目录,当pod节点删除时,volume的内+ | S # B ` c容也会被删除。但如果仅仅是容器被销毁,pod还在,则volume不会受到任何影响。说白了,empG V K } ^tyDir的数据持久化的生命周期和使用的pod一致。一] e M & {般是作为临时存储使{ + F F用。
2、HostA D r 1 5 e ePath数据- ^ ! k w ^ F卷类型
HostPath 类型的数据卷将 Pod(容器组)所在节点的文件系统上某一个文件或目录挂载进容器组(容器内部] , . A % w),类似于docker中的; ~ ( Jbind moW ? ` - Zunt挂载方式。

这种数据持久化的方式,使用场景不多,因为它增加了P 5 = y ] gpod与节点之间的耦合。

绝大多数容器组并不需要使用 hostPath 数据卷,但是少数情况下,hostPath 数据卷非常有用:

适用场景如下:

  • 某容器需要访问 Docker,可使用 hostPath 挂载( K V宿主} A y n节点的 /var/lib/dockx h A p X ! t o 8er
  • 在容器中运行 cAdvisor,使用 hostPat` Y W Y & ! B q ch 挂载宿主节点的 /sys

总言而之,一般对K8s集群本身的数据持久化+ f , 3 R = . w W和docker本身的数据持久化会使用这种方式$ x { b 9 ( b R

3、Persistent 数据卷类型
PersistentVolume(PV存储卷)是集群中的一块存储空间,由集群管理员管理W D . { q或者由Storage class(存储类)自动管理v H ) /,PV和pod、deployment、Service一样,都是一个资源对象。

既然有了PV这个概念,那么PVC(PersistentVolumeClaim)这个概念也不得不说一下,PVC代表用户使用存储的请求,应用申请PV持a @ Q h ? h久化空间的一个申请、声明。K8s集群可能会有多个PV,你需要不停的为不同的应用创建多个PV。

比如说,pod是消耗nodec 2 @ J节点的计算资源,而PVC存储卷声明是消耗PV的存v h 9储资源。Pod可以请求的是特定数量的计算资源(CPU或e * * ; V内存等),而PVC请求的是特定大小或特定访问模式(只能被单节点读写/可被多节点只读/可被多节点读写)的存储资源。

PV和PVC的关系
PV(存储8 f z X M 7卷)和PVC(存储卷声明)的关系如下图所示:
K8s——数据持久化

上图中的解释如下:

  • PV是集群中的存储资源,通常由集群p F g 4 6管理员创建和管理;
  • StorageClass用于对PV进行分T 9 X *类,如果配置正确,Storage也可以根据PVC的请求动态创建PV;
  • P} q i 7 L z KVC是使用该h Q H , q U 9 E资源的请求,通常由应用程序提出请求,并指定对应的StorageClassE 9 W 0 P g B a和需求的空间大小;
  • PVC可以作为数据卷的一种,被c n *挂载到pod中使用;

存储卷声明(PVC)的管理过程

PV和PVCr / & 7的管理过程描述如下:
1、在主机上划分出一个单独的目录用于O T nPV使用,并且定义其可用大小
2、创建PVC这个资源对象,以便请求PW m X | e g ! ]V的存储空间
3、pod中添加y N = _数据卷,数据卷关Z u 5 k = g L M +联到PVC;
4、Pod中包含容器,容器挂载数据卷

案例大概过程如下:
底层存储采用nfs存储,然后在nfs的目录下划分1G的容量供PV调度。然后通过创建PVC来申请PV的存储资源空间,最后创建pod测试,使用PVC声明的+ d 9 F存储资源来实现数据的持久化。

1)搭建nfs存储

//为了方便操作,我直+ Z h _ = u B接在master上搭建nfs存储
[root@docker-k8s01 ~]# yum -y install nfs-utils
[6 K ] Jroot@dockQ 5 C {er-k8s01 ~]# systeV , 3 Cmctl enable rpcbind
[root@docker-k8s01 ~]# vim /etc/exports
/nfsdata  *(rw,: A fsync,no_root_squash)
[root@docker-k8s01 ~]# systemctl rest? 5 Q ` d . hart nfs-server
[root@docker-k8s01 ~]# sy= 9 U ?  h ystemctl enable nfs-f B 8 # ( ~ -server
[root@docker-k8s01 ~]# showmount -e
Export list for dQ 0 l Y f ^ ? )ocker-k8s01:[ ] * V h &
/nfsd` I y 4ata *

2)创建PV% z 3 Z K资源对象

[root@docker-k8s01 ~]# ca ^ K lt test-pv+  f . q e ;.yaml
apiVersion: v1
kind: PerM ` &sistentVolume
meta) 2 / edata:
name: test-pv
spec:
capaci I : W @ -ty:
storage: 1Gi               // 该pv可分配的容量为1G
accessModes:
- ReadWritv J M x f 8eOnce              // 访问模式为只能以O h r m _ % v c读写的方式挂载到单个节点
persiB 6 n Y Q = R * -stentVolumeReclaimPolicy: Recycle            // 回收策略为Recycle
storageClG G : w z R cassNamE } * r k LeI z l m u j:/ ? u u nfs              // 定义存储类名字
nfs:             // 这里和上面存储类名称要一样
path: /nf5 4 k { i msdata/test-pv             //指定H J m m i ` M enfs目录
server: 192.168.171.151               // nfs服务器IP
//关于上述的具体解释
#caF & C & D 4 m wpacity:指定PV的大小
#AccessModes & T ( l hsE N # 6 5 z R:指定访问模式
#ReadWriteOnce:只能以读写的方式挂载到单个节点(单个节点意味- F 7 u着只能被单个PVC声明使用)
#ReadOnlyMany:能以只读的方式s - h挂载到多个节点
#ReadWrite` 9 : 9 v AMany:能以读写的方式挂载到多个节点
#persistentVolumeRe3 V t  F Ucla T Z &imPolicy:PV的回收策略{ e 5 O Q ?
#Recycle:清除PV中的数据,然后自动回收。u & * z . Q Y U M
#Retain^ r Y Z T } | 3:需要手动回收。
#Delete:删除云存储资源。(E - . x 9 I $ m云存储专用)
#PS:注意这里的回收策略是指,在PV被删除后,在这个PV下所存储的源文件是否删除。
#storageClassNameO a T .  S a o 2:PV和PVC关联的依据。
//执行yaml文件
[r2 ( @ + A ; O . ]oot@docker-k8s01 ~]#: # l ) x u kubectl apply -f te: ( ^ u {st-pv.yaml
[root@docker-k8s01 ~]# kubectl get pv teH p n - c Z % A zst-pv ^ m d } C M r ;
NAME      CAPACITY   ACCESS MODESI J 1 | F u k   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
test-pv   1Gi        RWO            Recycle          Avail/ d k uable           nfs                     71s
注:查看PV的状态必须为A! Y Rvailable才可以正常使用
注:查看PVO I 2 F v的状态必须为Available才可以正常使= w N | r { u用
注:查看PV的状态必须为Available才可以正常使用

3)创建PVC资源对象

[root@docker-k8s01 /]# cate ? K test-pvc.yaml
apiVersion: v1
ki! & y K E _nd: PersistentVolumeClaim
metadataU u * ^ u:
name: test-pvc
spec:
accessModeL # j Us:
- ReadWriteOnce        // 定义访问模式,必须和PV定义的访问模式一致
resources:
requests:
storage: 1Gi        // 直接请求使用最大的容量
storageCla3 I w f I j hssName: nfs        // 这里的名字必须和PV% P 2 q X ! / h定义的名字一致
//执行yaml文件
[root@docker-k8s01 /]# kubectl apply -f test-pvc.yaml
//再次查看PV及P8 [ m 9 eVC的状态(状态为bound,表示该PV正在被使用)
//查看PVC的状态
[roj P ? X yot@docker-k8s01 ~]# kubectl get pvc
NAME       STATUS   VOLUME    CAPACk e G j sITY   ACCESS MOD4 - ES   STORAGECLASS   AGE
tesA m K + y r 3 ? zt-pvc   Bound    tN o K } jest-t U R o + G e 9pv   1Gi        RWO            nfs            116s
//查看PV的状态
[root@docker-k8s01 ~]# kubectl get pv
NU b f L e P R ] KAME      CAPACITY   ACCESS MODES   RECLAO m #IM POLICY   STATUS   CLAIM              STORAGECLASS   REASON   AGE
te, P Vst-pv   1Gi        RWO            Recycle          Bound    default/) | 9 a g ) wtest-pvc   nfs                     7m48s

4)创建一个Pod

//这里创建的pod使用刚刚创建的PV. = B 5 5来实现数据的持久化
[root@docker-k8s01 ~]# cat test-pod.yamlV S T S ; ] m
apiVersion: v1
kind: Pod
metadata:
name: test-pod
speU ^ T )c:
containk : 4 4ers:
- n^ S O , y Kame: test-pod
image: busybox
args:
- /bin/sh
- -c
- -sleep 30000
volumeMounts:
- mountPath: /testdata
name: volumedata           // 自定义名称
volumes:
- name: volumedata          // 需要和上方定义的名称一样,是上述的解释
persistentVolumeClaim:
claimName: test-pvc
[root@docker-k8s01 ~]# kubecJ q B _ F  = )t^ _ 9 w B ll apply -f tn T i % #est-pod.yaml
//查看pod的状态
[root@docker-k8s01 ~]# kj { ? } K D s nubectl get pox # yd
NAME       READY   STATUS              RESTARTS   AGE
test-pod   0/1     Co6 P d BntainerCreating   0          72s
//咦,发现他状态5 o / K M一直处于ContainerCreating,这是为什么哩?
//当遇到pod状态不正常时,一般我们可以采用三种方式来排错
//第一就是使用kubG 0 | ( & s 3 ~ 6ectl  describe命令来查看pod的详细i x T 5 M * ` 3 d信息
//第二就是使用kubectl logq  G H ) Os命令来查R 1 a d ( Y看pod的日志
//第三就是查看宿主机本机的messageH q ;日志
//这里我采用第一种方法排错
[rA | . Ioot@docb d B { ? W ^ Mker-k8s01 ~]# kubectl describe pod test-pod
//输出的信息如下
mount.nfs: mounting 192.168.171.151:/nfsdata/test-pv failed,E i t Z x W i reason given by server: NX j * t { no such file or directory
//原来是我们在挂载nfs存储N E i w目录时,指定的目录并不存在
//那就在nfs服务器上(这里是本机)进行创建相关目录咯
[roott B G V @ 1 S r@docker-k8s01 ~]# mkdir -p /nfsd4 ; -ata/test-pv
[root@docker-k8s01 ~]# kubectl get pod test-pod
#如果pod的状态还是正在创建,那么就是因为运行该pod的节点r [ g上的kubelet组件还没有反应过来
#如果要追求pod的启动速度,可以手动将pod所在节点的kubelet组件进行重启即可。
/2 @ 4 d D S J w j/稍等片j t ^ l刻,再次查看,发现其pod已经running了
[root@docker-k8s01 ~]# kubectl get pod test-N f .pod
NAME       READY   STATUS    RESTARTS   AGE
test-pod   1/1     Running   0          21s

5)测试其数据持久化的效果

//进入Pod
[root@docker-k8s01 ~]#0 # [ A 7 b + T Q kubectl exec -it test-pod /bin/sh
/ # echo "test pv pvc" > /testdata/test.txt
//回到nfs服务器,查看共享的目录下是否有容器中写入的信息
[root@docke9 u 7 ? k G R k jr-k8s01 ~]# cat /nfs+ w S C ]  @ J Jdata/test-pv/test.txt
test pv pvc
//现在查看到pod容器的所在节点,( _ S w { i I r然后到对应节点将其删除
[root@docker-k8s01 ~]# kubectl get pod test-+ P J cpod -o wide
NAME       READY   STATUS    RESTARTS   AGE     IP           NODE           NOMINATED NODE   READINESS GATES
test-pod   1/1     Running   0          3m12sL 9 T U   10.244.1.2   docker-k8s02   <none&I ] _ O x K c }gt;           <none>
//在node01节点查看到其pod容器的ID号,然后将其删除
//获取容器ID
[root@dob [ ~ . B 0cker-k8s02 ~]# docker ps
//删除刚刚创建的容器
[root@docker-k8~ # Z 7 @ K , Ls02 ~]# docker rm -f df8c4ec00910
//查看nfs服务器,发现其本地目录下的数据还是在的
[root@docker-k8s01 ~]# cat /nfsdata/test-pv/test.txt
test p* P * w { F P Tv pvc
//那么现在测试,将这个pod删除,nfs本地的数据是否还在?
[root@docker-k= N M ] H e F K %8s01 ~]# kubectl delete -f test-pod.yay . C )ml
//可以看到,本地的数f = m g据还在
[root@docker-k8s01 ~M m p i]# cat /nfsdata/test-pv/test.txt
test pv pvc
//那现在要是将PVC删除呢?
[root@docker-k8s01 /]# kubectl delee g F ; J o m A Vte -f test-pvc.yaml
persistentvolumeclaim "te% = b H  {st-pvc" deleted
//可以看到,数据不见了
[root@docker-k8s01 /]# cat /nfsdata/test-pv/test.txt
cat: /nfsdata/test-pv/test.txt: No such file oT 7 ( ` 4 @ V !r direw # Lc0  5 $ S ? 5 gtory

由于我们在创建pv这个资源对象时,采用的回收策略是清除PV中的数据F n U a J,然后自动回) s 2收,而PV这个资源对象是由PVC来申请使用的,所以不管是容器也好,pod也好,它们的销毁并不会影响用于实现数据持久化的nfs本地目录下的数据,但是,一旦这个PVC被删除,那么本地的数据就会随着PVC的销毁而不复存在,也就是说,采用PV这种数据卷来实现数据的持久化,它这个数据持久化的生命周期是和PVC的生命周期是一致的。