容器技术干货┃K8s存储篇之PV(PVC)

简介: 前言 Kubernetes集群中,如果没有存储,所有pod中应用产生的数据都是临时的,pod挂掉,被rc重新拉起之后,以前产生的数据就丢掉了,这对有些场景是不可接受的,此时,外部存储就显得尤为重要。 这里重点介绍两个API资源: PersistentVolume(PV):集群中的一块网络存储,是集群中的资源,可类比集群中的Node资源; PersistentVolumeClaim(PVC) : 用户对存储的需求,可类比pod,pod消费node资源,PVC就消费PV资源。

前言

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)

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
5月前
|
存储 Kubernetes 网络安全
关于阿里云 Kubernetes 容器服务(ACK)添加镜像仓库的快速说明
本文介绍了在中国大陆地区因网络限制无法正常拉取 Docker 镜像的解决方案。作者所在的阿里云 Kubernetes 集群使用的是较旧版本的 containerd(1.2x),且无法直接通过 SSH 修改节点配置,因此采用了一种无需更改 Kubernetes 配置文件的方法。通过为 `docker.io` 添加 containerd 的镜像源,并使用脚本自动修改 containerd 配置文件中的路径错误(将错误的 `cert.d` 改为 `certs.d`),最终实现了通过多个镜像站点拉取镜像。作者还提供了一个可重复运行的脚本,用于动态配置镜像源。虽然该方案能缓解镜像拉取问题,
625 2
|
10月前
|
Kubernetes 调度 异构计算
生产环境 K8S + Deepseek 实现大模型部署 和 容器调度(图解+史上最全)
生产环境 K8S + Deepseek 实现大模型部署 和 容器调度(图解+史上最全)
生产环境 K8S + Deepseek 实现大模型部署 和 容器调度(图解+史上最全)
|
10月前
|
数据采集 消息中间件 Kubernetes
容器化爬虫部署:基于K8s的任务调度与自动扩缩容设计
随着业务复杂度提升,传统定时任务和手工扩缩容难以满足高并发与实时性需求。本文对比两种基于 Kubernetes 的爬虫调度与扩缩容方案:CronJob+HPA 和 KEDA。从调度灵活性、扩缩容粒度、实现难度等维度分析,并提供 YAML+Python 示例。方案 A(CronJob+HPA)适合固定定时任务,配置简单;方案 B(KEDA)支持事件驱动,适合高并发与异步触发场景。根据实际需求可混合使用,优化资源利用与效率。
368 4
|
11月前
|
存储 监控 对象存储
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
334 0
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
|
12月前
|
存储 监控 对象存储
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
260 1
|
11月前
|
存储 运维 Kubernetes
容器数据保护:基于容器服务 Kubernetes 版(ACK)备份中心实现K8s存储卷一键备份与恢复
阿里云ACK备份中心提供一站式容器化业务灾备及迁移方案,减少数据丢失风险,确保业务稳定运行。
|
6月前
|
Kubernetes Docker Python
Docker 与 Kubernetes 容器化部署核心技术及企业级应用实践全方案解析
本文详解Docker与Kubernetes容器化技术,涵盖概念原理、环境搭建、镜像构建、应用部署及监控扩展,助你掌握企业级容器化方案,提升应用开发与运维效率。
1026 108

相关产品

  • 容器服务Kubernetes版
  • 推荐镜像

    更多