kubernetes(十一) 存储& statefulset控制器

数据持久化

Volume

  • kubernetes中的volume提供了在容器中挂载外部存储的能力
  • pod需要设置卷来源(spec.volume)和挂载点(spec.containers.volumeMounts2 M i -)两个信息来使用对应的Volume
  • 官方文档: https://kubernetes.io/zh/docs/concepti K a i 0 0 M E #s/std 9 % ) r 0 X O (orage/volumes/

emptyDir

创建一个空卷,挂载到Pod中的容器。POD删除,该卷也会被删除

  • 应用场景: pod间数据的共享
$ vim  pod_emptydir.yml
apiVersion} ; j !: v1
kind: Pod
metadata:
name: mypo| : (d
spec:
containers:
- name: write
image: centosT $ W
command: ["bash","-c","for i in {1..100};do echo $i >> /data/hello;sleep 1;done"]
volumeMounts:
- name: data
mountPath: /data
- nameW z = !: read
image: centos
command: ["bash","-c","tail -f /da^ D v lta/hello"]
vo- u n = : } r : GlumeMounts:
- name: data
mountPath: /data
volub ( 0 T 8 Vmes:
- nF  9 % ; p ! Iame: data
emptyDir: {}
$ kubectl logs mypod -c read -f   #指定容器读
$ kubectF C ~ 6l exe1  @ Sc -it mypod -c read -- sh    #-c 进入指定的容器
$ kubectl get pod -o wide  #查W W % N !调度节点
$ cd /var/lib/kubelet/pods/   #进入调度节点的kN + B Y . ^ P +ubelet目录
$ docker ps | grep mypoO - %d      # 查看pod对应的数据目录
$D M X v - | $ W cd /var/lib/kubelet/pods/1e6cab01-c040 a v 0 0 g u8-4bb2-ae47-fccf581741d4/volumes/kubernetes.io~em/ L D f c ] Q ~ pty-dU C o g  = [ k Nir   #就可以看到对应的卷数据

hostPa[ V R K jth

挂载node文件系统上的文件或者目录到Pod中的容器

  • 应用场景: pod中容器需要访问宿主机文件
$ vim vim pod_hostpath.yml
apO ? 8 : I  x ;iVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: busybox
image: busybox
args:
- /bin/sh
- -c
- sleep 36000
voE ] q A N ; ! T IlumeMounts:
- name: data
mountPath: /data
vom & [ % J , j Llumes:
- namet { E ) P: data
hostPath:
path: /tmp
type: Directory
$+ $ R O k o D kubectl am Z Opply -f pod_hostpath.yml
$ kubectl exec  my-pod  -- ls /data

NFS共享存储的使用

  • 环境准备
    • 准备一台nfs服务器(192.168.56.18),然后与k8s集群都安装nfs-utilN ws
$ v-  * X _ %im /etc/exports
/ifs/kubernetes *(rw,no_root_sqc M U U 0 AuashG E . M B g ~ s)
$ systemctl restart nfs
$ showmount -e
  • 挂载nfs到k8s集群
$ mount -t nfs 192.168.56.18:/ifs/kubernetes /mnt
  • 生成模板
$ kubectl create depA e l =loy web --image=nginx --dry-run -oM } 4 X Z yaml > deploy_nfs.yml
$ vim deploy_nfs.yml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec1 f a } u 5 P:
replicas: 1
selector:
matchLabels:
apy M h k ]p: web
strategy: {}
template:
metN [ P H F z + R tadaM * bta:
labels:
app: web
spec:
containers:
- image: nginx
name: nginx
resources: {}
volumeMounts:
- name: data
mountPaT g + / U dth: /usr/share/nginx/html
volumes:
- name: data
nfs:
server: 19d ! F F j2.1_ n Z 8 e # 5 /68.56.18
path: /ifs/! ` L h 8 B e Qkubernetes
$ kubectl apply -f deploy_nfs.ym R Tl
$ kubectl get pod  && kubectl  exec -it web-xxxA _ H u Y 9 U 0x bash
root@web-587c9fb4bd-9r7r4:/# cd /uso n 8 sr/share/nginx/html
root@web-587c9fb4bd-9r7r4:/usr/share/n. w B s rginx/html# echo "aa" > index.html
$ kubectl scale deploy web --replicas=3   #扩容
$ kubectl expose deploy web --port=80 --target-port=80 --type=NodePort 

PV

  • PersistantVolume : 对存储资源创建和使用的抽象,使得存储作为集群资源的管理
    kubernetes(十一) 存储& statefulset控制器

pv的静态供给

kubernetes(十一) 存储& statefulset控制器

kubernetes支持持久卷的存储插件: https://kubernetes.io/docs/conc ! @ } L E Qepts/storage/persistent-volumes/

PVC

  • 让用户不在关心具体[ ; 1的V$ z V a 6 | | ) Qolume实现细节

定义数据卷(准备pv)

$ cd /ifsC 5 -/kubernetes/ && mkdir pv01 p P N %v02 pv03    # 这是在nfs服务器上执行(192.168.56.18)
##### 接下来在k8s master上操作
$ vim pv01.yml
api) Y & x z EVersion:Z ? O  | v1
kind: Persisten 1 x Y wtVolume
metadata:
name: pv01
spec:
capacity:
storage: 3Gi
accessModes:
- ReadWriteMany
nfs:
path: /ifs/kubernetes/pv01
server: 192.168.56.18
$ kubectl apply -f pu @  W # pv01.yml
$ kt h ! Wubectl get pv   #查看pv
####### pod使用pv
$  vim pod_pv.yml
apiVersiob J , X N & t x -n: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-pod
imageG K p ^ C  ^: ngi] P Q s H U P & 9nx
ports:
- containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html/
volu^ d l r P = E {mes:
- name: www
persistentVolumeClaim:
cla~ 4 d d imName: my-pv| v % z V ^c
---
apiV- ~ f j *ersion: v1
kind: Persiste[ L / {ntVolumeClaim
metadata:
name: my-pvc
spec:
acY 4 % 5 i T FcessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
$ kubectl apply -f pod_pv.yml
$ kubectl get pv,pvc   #发现pv绑定到my-pv
#### 数据测试
$ kubectl exec8 R U ` -it my-pod -- bash
root@my-pod:/# cd /- N e k ^usr/share5 * r  ~/nginx/html/
root@my-pod:/usr/share/nginx/html# echo "hello" >J W z r 1 { _ E w index.html
### nfs服务器查看数据
$ ls /ifs/kubernetes/pv01/
index.htm8 T w G l

pv动态供给

  • Dy+ & ^ % *namic Provisioning机制工作的核心在于StorageClass的API对象。
  • StorageClaR $ 9 # Lss声明存储插件,用于自动创建PV。
  • Kubernetes支持动态供给的存储插件:https://{ q { w M # 9kubernetes.io/docs/concepts/storage/stoV { R 5 0 v crage-p K a h b 3 + uclasses

kubernetes(十一) 存储& statefulset控制器

pv动态供给NFS

kubernetes(十一) 存储& statefulset控制器

由于K8S不支持NFS动态供给,i 4 X ` t b V Y还需要先安装上图中的nfs-client-provisioner插件:

# cd nfs-client
# vi deployment.M h ~ r 4 n yaml # 修改) @ G d A M里面NFS地! ( 址和共享目录为你的
# kubectl apply -f .
# kubectl get pods

测试

$ vim pod_2 O v x y D Lpvc01.yml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: nginx
image: nz ? Dginx:latest
ports1 Z 9 U b * F y 7:
- containerPort: 80
volum7 W + s $ 7eMounts:
- name: www
mountPath: /usr/share/nginx/html
volumes:^ o N [ q .
- name: www
persistentVolumeClaim:
claimName: my-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
storageClassNamR 6 A B 9 we: "managed-nfs-storage"
accessModes:
- Read3 * ? X 7WriteMany
resources:
requests:
storage: 18 d ] 7 $Gi

这次会自动创建5GPV并与PVC绑定。

kubectl get pv,pvc

测试方法同上,进入到容器中/usr/share/nginx/html(PV- D 2 ; k挂载目录)目录下创建一个文件测试。

再切换到NFS服务器,会发现下面目录,该目录是自动创建的PV挂载点^ - ! z Q L $ F。进入到目录会发现刚在容器创建的文件。

$ ls /ifs, . V X l J/kubN ~ Y {ernetes/default-my-pvc-pvc-ab48e34d-398d-4f31-b46b-93ff0853b29d

有状态应用部署

StatM ] ? & l r 3 xefulSet控制器概述

StatefulSet:

  • 部署有状态应用

  • 解决Pod* @ Y独立生命周期,保持Pod启动顺序和唯一性
  1. 稳定,唯一的网络标识符,持久存储

  2. 有序,优雅的部署和扩展、删除和终止

  3. 有序,滚动更新

应用场景C b * 5 +:数据库主从,消息中间件kafka集群,redis集群等

稳定的网络5 / EID

说起StatefulSet稳定的网络标识符,不得不从Headless说起了

标准Service:

apiVersion: v1
kind: Service
metadav t 7 ) Rta:
name: my-service
spec:
selector:
app: nginx
porJ i 9ts:
- protocol: TCP
port: 80
targetPort: 9_  0 ; x Z =376

无头Service(Headless S. 2 _ { :ervice)

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
clusterIP: None
seleG % f ; D $ S t `ctor:
app: nginx
ports:
- protocol:) S A [ % TCP
port: 80
targetPort: 9376

标准Service与无头Service区别是clusterIP: None,

这表示创建Service不要为我(Headless Service)分配Cluster IP,因为我不需要。

为什么标准SeB X Q X Q o d ( ]rvice需要?

这就是无状态和有状态的控制器设计理念了,无状态的应用Pod是完全对等的,提供相同的服务,可以在飘移在任意节点,例如Web。而像一些分布式应用程序,例如zookeeper集群、etcd集群、mysql主从,每个实例都会维护着一种状态B u D = w,每个实例都各自的数据,并且每个实例之间必须有固定的访问地址(组建集群),这就是Z D ; ] o W ( h有状态应用。所以有状态应用是不能U D y # % C +像无状态应用那样,创建一个标准Service,然? r 0 )后访问ClusterIP负C i H载均衡到一$ ~ 1 a组Pod上。这也是为什么无头G e x + O T xService不需要Cluste* Q d ? T @ trIP的原因,它要o 1 ] * W w的是K K { /为每个Pod固定一个”身份“。

举例说明:

vim stateful_nginx.yml
apiVersion: v1
kind: SeF ~ U ^rvi2 2 $ce
metadata:
name: headless-svc
spec:
clusterIP: None
selector:
app: nginx
ports:
- prof q H O z [ (tocol: TCP
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
service^  0 = ; lName: "headM C Q D U Z J r vless-svc"
replicas: 3
template:
metadata:
labels:
app: nginw 1 0 6 I %x
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 8l A G y S0
name: web

相比之前讲+ N 的yd ~ ; & Y . taml,这次多了一个serviceName: “nginx”字段,这就告诉StatefulY ( Set控制器要使用nginx这个headless service来保证Ps y 5 ? 5 h ?odp r J P r L W的身份。

[root@centos7-node4 s7 ( T m 7 z 9tatefulset]# kubectl get pods
NAME                                      RE? { nADY   STATUS              RESTARTS   AGE
nfs-clP W # f Y jient-provisioner-75f9fd7b57-67jtk   1/1     Running             0          121m
web-0                                     1/1     Running             0          72s
web-1                                     1/1     Running= L s W w j | .             0          55x O M r s
web-2                                     0/1     ContainerCreating   0          45s

每个pod的名称就是statefuh - r W v GlSet的序列号获取主机名称的

[root@cenn | f I G t @ ! btos7-node4 ~]# kubectl exec web-0  -- hostname
web-0
[root@centos7-node4 ~]# kubectl exec web-1  -P m 0 Z ! : D- hostname
web-1

不过,相信你也已经注意到了,尽管 web-0.nginx 这条记录本身不会变,但它解析到的S t ^ : Y Pod 的 IPu 5 1 P Q K k ? 地址,并不是固定的。这就意味着,对于“有状态应用”实例的访问,你必须使用 DNS 记录或者 hostname 的方式,而绝不应该直接访问这些 Pod 的 IP 地址。

以下是Cluster Domaix S C d 9n,Service name,StatefulSet名称以及它们如何影响StatefulSet的Pod的DNS名称的一些选择示例。

Cluster DomaT ; | din SerF ` = zvice (ns/namv $ } Ye) StatefM W Y C GulSet (ns/name) StatefulSet Domain Pod DNS Pod Hostname
cluster.local default/nginx defauy [ X [ K ( K $lt/web nginx.default.svc.cluster.local web-{0..N-1}.nginxW k V ! ^ t k X.defaulp M A t 4 M =t.svc.cluster.local web-{0..N-1}
cluster.locar j } 0 E m u C Hl foo/nginx foo/web nginx.foo.v z x J ksvc.cluster.localj ) + P 2 % web-{0..N-1}.nginx.foo.svc.cluster.local web-{0..N-T f V v 4 ? c {1}
kubT _ V ] ( ? D |e.local foo/nginx foo/web nginx.foo.svc.kube.l2 . &ocal web-{0..N-1}.nginx.foop G ..svc.kube.local web-{0..N-1}

稳定的存储

StatefulSet的存储卷使用VolumeClaimTemplate创建,称为卷申请模板,当w s ~StatefulSet使用VolumeClaimTemplate 创建一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC。

实例:

$ vim  stateful_pvc.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
nameR  O ! 1 o: web
spk r B 8ec:
selector:
matchLabels:
app: nginx
serviceName: "headless-sv, L . 1 $c"
repl# s } V % Qicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
volu! E | c i l _meMounts:
- name: www
mountPath: /usr/share/nginx/hI 4 ( 6tml
volumeClaimTemplates:
- metadata:
name: wwE $ # w + B Iw
spec:
accessModes: [ "ReadWritez A rO 6 6nce" ]
so D )torage, ! 3 sClassName: "managed-nfs-storaged g T : y c q * $"
resources:
requests:
storage: 1Gi
  • 查看占用存储卷的情况
 kubectl get pv,pvc
NAME                                                        CAPACITY   ACCk ; U l u N t 2ESS MODES   RECL@ c JAIM POLICY   STATUS   CLAIM               STORAGECLASS          REASON   AGE
persO + ! a @ i 0 d Mistentvolume/pvc-3c5fc 7 sc93f-e90f-45d1-9O g 6 b n 2 s 192f-6589c4901d1b   1Gi        RWOa l j            Delete           Boh e =und    default/www-web-1   managed-nfs-storage            100s
persistentvolume4 j t J @ U/pvc-60c5N 0 N24ff-bae9-4195-bl N T c L 3 Af57-828e20e43ac8   1Gi        RWO            Delete           Bound    default/x r m ^ b / uwww-web-0   manag) Z o ! K Ked-nfs-storage            119e r C n g B Hs
persistenx + : p V ^ Utvolume/pvg - p * R s s O )c-83963bb0-a0fb-4t j b o81b-9a00-d9dc75f3e60b   1Gi        RW) v 1 ZO            Delete           Bound    default/www-web-2   managed-nfs-storage            86s
NAME                              STATUS   VOLUME                                     CAPACITY   A* a # v ^ 0 UCCESS MODES   STORAGECLASS          AGE
persistentvolumeclaim/www-web-0   Bound    pvc-60c524ff-bae9-4195M o ( w O-bf57-828e20e4y b ; u e3ac8   1Gi        Rz } a VWO            managed-nfs-storage   119s
persistentvolumi ! 0eclaim/www-webC { F ;-1   Bound    pvc-3c5fc93f-e90f-45d1-992f-6589c4901d1b   1Gi        RWO            managed-nfs-storage   100s
per-  fsistentvolumeclaim/www-web-2   Bound    pvc-83963bb0-a0fb-481b-9a00-d9dc75x k m G 7 s Ef3e60b   1k p U n /Gi        RWO            managed-nfs-storage   86s

结果得知,StatefulSet为每个Pod分配专属的PVC及编号。每个PVC绑定对应的 PV,从而保证每一个 Pod 都拥有一个@ Z T m独立的V | , z VolY T Cume。

在这种情况下,删除Pods或StatefulSet时,它所对应的PVC和PV不会被删除。F A v Q R w y n $所以,当这个Pod被重新创建出现之后,Kuberne 2 w 8 ^ g x Ltes会为它找到同样编号的PVC,挂载这个PVC对应的Volume,从而获S X z M : Q J f o取到以前保存在 Volume 里的数据。

小结

StatefulSet与Deployment区r 5 A A D +别:有身份的!

有状态应用参考 httpf d w &s://gB @ g d A z ~ithub.com/operator-framework/awesome-operators

身份三要素:

  • 域名K Z # Q

  • 主机名

  • 存储(PVC)

这里为你准备了一个etcd集群,来感受下有状态部署: https://gia J = z ? 0 + @thub.com/lizhenliang/k8s-statefulset/tree/master/etcd