容器技术干货┃K8s存储篇之PV(PVC)
前言
Kubernetes集群中,如果没有存储,所有pod中应用产生的数据都是临时的,pod挂掉,被rc重新拉起之后,以前产生的数据就丢掉了,这对有些场景是不可接受的,此时,外部存储就显得尤为重要。
这里重点介绍两个API资源:
PersistentVolume(PV):集群中的一块网络存储,是集群中的资源,可类比集群中的Node资源;
PersistentVolumeClaim(PVC) : 用户对存储的需求,可类比pod,pod消费node资源,PVC就消费PV资源。
当然还有StorageClass等概念,这里不做详细说明(稳定后,后期文章专门介绍)。K8s存储管理主要分布在两个组件中(这里不包括api):kube-controller-manager和 kubelet。由于涉及的点比较多,我们分成几篇文章来介绍,本篇主要分析PersistentVolume。
代码基于社区,commit id: 65ddace3ed8e7c25546d12912c8dfdcd06ffe1e0
用例
Kubernetes支持的外部存储非常的多,如:AWSElasticBlockStore,AzureFile,AzureDisk,CephFS,Cinder,FlexVolume,GCEPersistentDisk,Glusterfs,HostPath,iSCSI,NFS,RBD,VsphereVolume等。
简单起见,以HostPath存储的方式,举例说明。
创建PV(hostpath方式存储,目录 /tmp/data) Yaml文件: kind: PersistentVolume
apiVersion: v1
metadata:
name: task-pv-volume
labels:
type: local
spec:
capacity:
storage: 10Gi
accessModes:
– ReadWriteOnce
hostPath:
path: “/tmp/data”
创建命令: kubectl create -f http://k8s.io/docs/tasks/configure-pod-container/task-pv-volume.yaml
persistentvolume “task-pv-volume” created
查看结果: kubectl get pv task-pv-volume
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM REASON AGE
task-pv-volume 10Gi RWO Retain Available 17s
创建PVC Yaml文件:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: task-pv-claim
spec:
accessModes:
– ReadWriteOnce
resources:
requests:
storage: 3Gi
创建命令: kubectl create -f http://k8s.io/docs/tasks/configure-pod-container/task-pv-claim.yaml
persistentvolumeclaim “task-pv-claim” created
查看结果:(已经绑定上面创建的PV) kubectl get pvc task-pv-claim
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
task-pv-claim Bound task-pv-volume 10Gi RWO 5s
PVC配置中没有指定volume name,PVController会从所有的volume中,找到合适的,和PVC进行绑定。
再查看上面创建的PV:
kubectl get pv tasck-pv-cvolume
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM REASON AGE
task-pv-volume 10Gi RWO Retain Bound default/task-pv-claim 8m
创建Pod,使用上面创建的PV: Yaml文件:
kind: Pod
apiVersion: v1
metadata:
name: task-pv-pod
spec:
volumes:
– name: task-pv-storage
persistentVolumeClaim:
claimName: task-pv-claim
containers:
– name: task-pv-container
image: nginx
ports:
– containerPort: 80
name: “http-server”
volumeMounts:
– mountPath: “/usr/share/nginx/html”
name: task-pv-storage 注:pod和PVC要在同一个namespace中。
创建命令: kubectl create -f http://k8s.io/docs/tasks/configure-pod-container/task-pv-pod.yaml
pod “task-pv-pod” created
查看pod: kubectl get pod task-pv-pod
NAME READY STATUS RESTARTS AGE
task-pv-pod 0/1 ContainerCreating 0 19s
kubectl get pod task-pv-pod
NAME READY STATUS RESTARTS AGE
task-pv-pod 1/1 Running 0 1m
进入pod,创建文件: kubectl exec -it task-pv-pod — /bin/bash
root@task-pv-pod:~# cd /usr/share/nginx/html/
root@task-pv-pod:/usr/share/nginx/html# echo “hello world” >pv.log
root@task-pv-pod:/usr/share/nginx/html# ls
pv.log
然后退出pod(容器),看下host是否有此文件:
root@task-pv-pod:/usr/share/nginx/html# exit
exit
nickren@nickren-thinkpad-t450:/tmp/data$ cd /tmp/data/
nickren@nickren-thinkpad-t450:/tmp/data$ ls
pv.log
nickren@nickren-thinkpad-t450:/tmp/data$ cat pv.log
hello world
由此可见,pod中的信息,已经存在了host里面。
代码
PV(PVC)即 PersistentVolume(PersistentVolumeClaim),在k8s中,有专门的controller管理他们。
下面分析创建Controller以及Controller管理PV(PVC)的逻辑
1、创建并启动PersistentVolumeController
func StartControllers(controllers map[string]InitFunc, s *options.CMServer, rootClientBuilder, clientBuilder controller.ControllerClientBuilder, stop <-chan struct{}) error {
。。。。。。
if ctx.IsControllerEnabled(pvBinderControllerName) {
// alphaProvisioner本应该在v1.5版本中去掉的,没做,现在有PR正在做,可以//不用关心
alphaProvisioner, err := NewAlphaVolumeProvisioner(cloud, s.VolumeConfiguration)
if err != nil {
return fmt.Errorf(“an backward-compatible provisioner could not be created: %v, but one was expected. Provisioning will not work. This functionality is considered an early Alpha version.”, err)
}
//构造ControllerParameters
params := persistentvolumecontroller.ControllerParameters{
KubeClient: clientBuilder.ClientOrDie(“persistent-volume-binder”),
SyncPeriod: s.PVClaimBinderSyncPeriod.Duration,
AlphaProvisioner: alphaProvisioner,
VolumePlugins: ProbeControllerVolumePlugins(cloud, s.VolumeConfiguration),
Cloud: cloud,
ClusterName: s.ClusterName,
VolumeInformer: sharedInformers.Core().V1().PersistentVolumes(),
ClaimInformer: sharedInformers.Core().V1().PersistentVolumeClaims(),
ClassInformer: sharedInformers.Storage().V1beta1().StorageClasses(),
EnableDynamicProvisioning: s.VolumeConfiguration.EnableDynamicProvisioning,
}
//这里创建PersistentVolumeController
volumeController := persistentvolumecontroller.NewController(params)
//启动PersistentVolumeController goroutine
go volumeController.Run(stop)
time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter))
} else {
glog.Warningf(“%q is disabled”, pvBinderControllerName)
}
。。。
}
在kube-controller-manager的StartControllers()函数中,构造PersistentVolumeController 并运行PersistentVolumeController goroutine。
2、PersistentVolumeController构造方法
func NewController(p ControllerParameters) *PersistentVolumeController {
。。。
//构造PersistentVolumeController
controller := &PersistentVolumeController{
volumes: newPersistentVolumeOrderedIndex(),
claims: cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc),
kubeClient: p.KubeClient,
eventRecorder: eventRecorder,
runningOperations: goroutinemap.NewGoRoutineMap(true /* exponentialBackOffOnError */),
cloud: p.Cloud,
enableDynamicProvisioning: p.EnableDynamicProvisioning,
clusterName: p.ClusterName,
createProvisionedPVRetryCount: createProvisionedPVRetryCount,
createProvisionedPVInterval: createProvisionedPVInterval,
alphaProvisioner: p.AlphaProvisioner,
claimQueue: workqueue.NewNamed(“claims”),
volumeQueue: workqueue.NewNamed(“volumes”),
}
//初始化相应的 VolumePlugin
controller.volumePluginMgr.InitPlugins(p.VolumePlugins, controller)
。。。
//给VolumeInformer 添加相应的event handler
p.VolumeInformer.Informer().AddEventHandlerWithResyncPeriod(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { controller.enqueueWork(controller.volumeQueue, obj) },
UpdateFunc: func(oldObj, newObj interface{}) { controller.enqueueWork(controller.volumeQueue, newObj) },
DeleteFunc: func(obj interface{}) { controller.enqueueWork(controller.volumeQueue, obj) },
},
p.SyncPeriod,
)
。。。
//给ClaimInformer 添加相应的event handler
p.ClaimInformer.Informer().AddEventHandlerWithResyncPeriod(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { controller.enqueueWork(controller.claimQueue, obj) },
UpdateFunc: func(oldObj, newObj interface{}) { controller.enqueueWork(controller.claimQueue, newObj) },
DeleteFunc: func(obj interface{}) { controller.enqueueWork(controller.claimQueue, obj) },
},
p.SyncPeriod,
)
。。。
return controller
}
3、PersistentVolumeController goroutine运行流程
func (ctrl *PersistentVolumeController) Run(stopCh <-chan struct{}) {
。。。
//从etcd中取数据初始化Controller缓存
ctrl.initializeCaches(ctrl.volumeLister, ctrl.claimLister)
//运行volume具体工作的goroutine
go wait.Until(ctrl.volumeWorker, time.Second, stopCh)
//运行claim具体工作的goroutine
go wait.Until(ctrl.claimWorker, time.Second, stopCh)
<-stopCh
ctrl.claimQueue.ShutDown()
ctrl.volumeQueue.ShutDown()
}
3.1、volumeWorker具体工作 func (ctrl *PersistentVolumeController) volumeWorker() {
workFunc := func() bool {
//从volume队列中取出一个volume object
keyObj, quit := ctrl.volumeQueue.Get()
if quit {
return true
}
defer ctrl.volumeQueue.Done(keyObj)
key := keyObj.(string)
glog.V(5).Infof(“volumeWorker[%s]”, key)
。。。
//检查此volume是否还存在
volume, err := ctrl.volumeLister.Get(name)
if err == nil {
//volume 存在于 informer cache,更新volume
ctrl.updateVolume(volume)
return false
}
if !errors.IsNotFound(err) {
glog.V(2).Infof(“error getting volume %q from informer: %v”, key, err)
return false
}
//volume不存在,删除此volume
。。。
ctrl.deleteVolume(volume)
return false
}
。。。
}
3.2、 claimWorker具体工作 类似volumeWorker工作流程
func (ctrl *PersistentVolumeController) claimWorker() {
workFunc := func() bool {
//从claim队列中取出一个claim object
keyObj, quit := ctrl.claimQueue.Get()
if quit {
return true
}
defer ctrl.claimQueue.Done(keyObj)
key := keyObj.(string)
glog.V(5).Infof(“claimWorker[%s]”, key)
namespace, name, err := cache.SplitMetaNamespaceKey(key)
。。。
//检查此claim是否还存在
claim, err := ctrl.claimLister.PersistentVolumeClaims(namespace).Get(name)
if err == nil {
//claim存在于 informer cache,更新claim
ctrl.updateClaim(claim)
return false
}
if !errors.IsNotFound(err) {
glog.V(2).Infof(“error getting claim %q from informer: %v”, key, err)
return false
}
//claim不存在,删除此claim
ctrl.deleteClaim(claim)
return false
}
。。。
}
K8s中PersistentVolumeController的主体逻辑就是上面所述,具体的update、delete操作由于涉及较多的函数,篇幅所限,不一一列举,可自行阅读代码。
本文转移K8S技术社区-容器技术干货┃K8s存储篇之PV(PVC)

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
用K8S解决容器的混乱(下)
Kubernetes编排 Kubernetes(K8S)是一个开源容器编排系统,它使用期望状态理念来运行所有的应用程序容器。Kubernetes是由Google创建的,旨在集合工程师们在过去10年中构建自己容器编排系统(Brog)所获得的经验。Kubernetes不是这个系统的端口或转换。相反,它本意是尝鲜,开始就想避免错误并分享经验。它从与RedHat、CoreOS、IBM、Mesosphere和Microsoft的合作中受益匪浅。 Kubernetes有很多组件,下面探讨一些最为重要的。 容器 容器是Kubernetes中的“亚原子”组件,这意味着你不会只运行一个容器,因为它们必须在一个称为pod的控制结构中运行。但是你可以用与在Docker中一样的方式,来基于Dockerfiles运行Docker映像。我经常用Makefiles在Docker中运行一个容器来进行开发,然后将它启动到Kubernetes中进行生产。 Pod Pod是Kubernetes的“原子”单位。Kubernetes的其他组件启动一个或多个pod,或将一个或多个pod连接到网络。Pod由一个或多个容器组成。在...
- 下一篇
同是容器管理系统,Kubernetes为什么那么火?
Kubernetes允许用户随处部署云原生应用程序,并以其偏好的方式对加以管理。 正如大多数现代软件开发人员通过实践所证明,容器技术确实能够为用户在物理及虚拟基础设施之上运行云原生应用程序提供更为理想的灵活性。容器技术能够将多层服务打包为一款应用程序,并确保其能够在不同计算环境之间往来移植,从而实现开发/测试,及生产性使用。 利用容器技术,用户能够更轻松地启动应用程序实例,快速满足峰值需求。另外,由于容器直接使用主机操作系统资源,体量较虚拟机更轻,这意味着容器能够高效发挥底层基础设施的固有优势。 说到这里,容器的前途似乎一片光明。然而必须承认,尽管容器运行时API非常适合管理单个容器,但一旦面对着分布在多台主机上且拥有数百套容器的大规模应用程序时,这些API就会变得力不从心。在这种情况下,容器需要接受管理并有序接入外部环境,从而实现调度、负载均衡以及分配等任务——Kubernetes正是能够实现上述目标的一款容器编排工具。 作为一套用于容器化应用程序部署、扩展与管理工作的开源系统,Kubernetes能够处理计算集群上的容器调度与工作负载管理,确保其严格按照用户的意愿运行。 Kube...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2整合Thymeleaf,官方推荐html解决方案