源码版本
kubernetes version: v1.3.0
简介
前一节介绍了Garbage Collection,涉及到的策略基本与磁盘资源有关。对于k8s集群如何高效的利用各种资源,也非常值得我们涉猎学习。管理好资源才能更好的创建服务,所以这节继续学习kubelet的diskSpaceManager。
diskSpaceManager顾名思义就是管理磁盘空间的,实际它的实现较为简单,就是给kubelet所在的节点预留磁盘空间的,当该节点磁盘空间低于该值时,将拒绝Pod的创建。策略初始化
跟GC介绍的套路一样,先从策略入手。
相关的结构如下:type DiskSpacePolicy struct { // free disk space threshold for filesystem holding docker images. DockerFreeDiskMB int // free disk space threshold for root filesystem. Host volumes are created on root fs. RootFreeDiskMB int}
该结构的出厂设置在cmd/kubelet/app/server.go中的UnsecuredKubeletConfig()接口进行。
func UnsecuredKubeletConfig(s *options.KubeletServer) (*KubeletConfig, error) {... diskSpacePolicy := kubelet.DiskSpacePolicy{ DockerFreeDiskMB: int(s.LowDiskSpaceThresholdMB), RootFreeDiskMB: int(s.LowDiskSpaceThresholdMB), }...}
赋值的KubeletServer的LowDiskSpaceThresholdMB参数的初始化在cmd/kubelet/app/options/options.go中的NewKubeletServer()接口中进行:
func NewKubeletServer() *KubeletServer { return &KubeletServer{ ... LowDiskSpaceThresholdMB: 256, ... }}
diskSpaceManager保留磁盘空间的默认值是256MB,当然这个值我们也可以手动修改,这些kubelet的手动配置都是在cmd/kubelet/app/options/options.go中的AddFlags()接口:
func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) {... fs.Int32Var(&s.LowDiskSpaceThresholdMB, "low-diskspace-threshold-mb", s.LowDiskSpaceThresholdMB, "The absolute free disk space, in MB, to maintain. When disk space falls below this threshold, new pods would be rejected. Default: 256")...
所以手动的话,需要修改kubelet命令flags中的low-diskspace-threshold-mb。
diskSpaceManager初始化
先介绍diskSpaceManager:
type diskSpaceManager interface { // Checks the available disk space IsRootDiskSpaceAvailable() (bool, error) IsRuntimeDiskSpaceAvailable() (bool, error)}
跟前一章的套路一样,该diskSpaceManager是个interface。
具体的初始化需要查看pkg/kubelet/kubelet.go中的NewMainKubelet()接口。调用流程: main -- app.Run -- UnsecuredKubeletConfig --> RunKubelet -- CreateAndInitKubelet -- NewMainKubeletNewMainKubelet接口如下:func NewMainKubelet( hostname string, nodeName string,...) (*Kubelet, error) {... diskSpaceManager, err := newDiskSpaceManager(cadvisorInterface, diskSpacePolicy) if err != nil { return nil, fmt.Errorf("failed to initialize disk manager: %v", err) }...}
继续查看newDiskSpaceManager()接口,该接口传入了cadvisorInterface和diskSpacePolicy参数,比较好理解这些参数,因为磁盘管理需要使用cAdvisor来获取磁盘信息,而diskSpacePolicy就是上面介绍策略初始化的结构。
该接口在pkg/kubelet/disk_manager.go中,继续查看源码:func newDiskSpaceManager(cadvisorInterface cadvisor.Interface, policy DiskSpacePolicy) (diskSpaceManager, error) { // 检查策略参数是否有效 // 实际就是判断下是否 < 0 err := validatePolicy(policy) if err != nil { return nil, err } dm := &realDiskSpaceManager{ cadvisor: cadvisorInterface, policy: policy, cachedInfo: map[string]fsInfo{}, } return dm, nil}
该接口的返回值是diskSpaceManager,而实际返回是realDiskSpaceManager结构体。
所以所有diskSpaceManager的接口实现需要根据realDiskSpaceManager进行查看。realDiskSpaceManager结构如下:type realDiskSpaceManager struct { // 用于获取磁盘相关信息 cadvisor cadvisor.Interface // 用于缓存文件系统信息 cachedInfo map[string]fsInfo // cache of filesystem info. // 操作锁 lock sync.Mutex // protecting cachedInfo. // 磁盘管理策略 policy DiskSpacePolicy // thresholds. Set at creation time.}
查看下realDiskSpaceManager结构实现的diskSpaceManager接口:
// 查看Runtime占用的磁盘空间是否够用func (dm *realDiskSpaceManager) IsRuntimeDiskSpaceAvailable() (bool, error) { return dm.isSpaceAvailable("runtime", dm.policy.DockerFreeDiskMB, dm.cadvisor.ImagesFsInfo)}// 查看根目录的磁盘空间是否够用func (dm *realDiskSpaceManager) IsRootDiskSpaceAvailable() (bool, error) { return dm.isSpaceAvailable("root", dm.policy.RootFreeDiskMB, dm.cadvisor.RootFsInfo)}
上面两个方法最终都调用了dm.isSpaceAvailable(),该接口使用了3个参数:
文件系统类型
磁盘保留空间大小,用于判断是否有效
cAdvisor的接口,用于获取RootFS和ImagesFs使用的磁盘情况
而该接口返回的是一个Bool值,true or false。
接口如下:func (dm *realDiskSpaceManager) isSpaceAvailable(fsType string, threshold int, f func() (cadvisorapi.FsInfo, error)) (bool, error) { fsInfo, err := dm.getFsInfo(fsType, f) if err != nil { return true, fmt.Errorf("failed to get fs info for %q: %v", fsType, err) } // 有效值判断 if fsInfo.Capacity == 0 { return true, fmt.Errorf("could not determine capacity for %q fs. Info: %+v", fsType, fsInfo) } if fsInfo.Available < 0 { return true, fmt.Errorf("wrong available space for %q: %+v", fsType, fsInfo) } // 判断该文件系统可用的磁盘空间是否小于最小预留空间 if fsInfo.Available < int64(threshold)*mb { glog.Infof("Running out of space on disk for %q: available %d MB, threshold %d MB", fsType, fsInfo.Available/mb, threshold) return false, nil } return true, nil}
继续看dm.getFsInfo()接口:
func (dm *realDiskSpaceManager) getFsInfo(fsType string, f func() (cadvisorapi.FsInfo, error)) (fsInfo, error) { dm.lock.Lock() defer dm.lock.Unlock() // 先查看缓存中的文件系统信息 // 需要比较该信息的时间有效性,为2s内 fsi := fsInfo{} if info, ok := dm.cachedInfo[fsType]; ok { timeLimit := time.Now().Add(-2 * time.Second) if info.Timestamp.After(timeLimit) { fsi = info } } // 2s之外的话,需要调用cAdvisor接口重新获取磁盘信息 if fsi.Timestamp.IsZero() { // 该f()接口作为参数传入,不同的文件系统对应不同的接口 // runtime: dm.cadvisor.ImagesFsInfo // rootfs: dm.cadvisor.RootFsInfo fs, err := f() if err != nil { return fsInfo{}, err } fsi.Timestamp = time.Now() fsi.Usage = int64(fs.Usage) fsi.Capacity = int64(fs.Capacity) fsi.Available = int64(fs.Available) // 更新cache dm.cachedInfo[fsType] = fsi } return fsi, nil}
diskSpaceManager实现
上面的初始化可以看到diskSpaceManager的两个关键性接口IsRuntimeDiskSpaceAvailable()和IsRootDiskSpaceAvailable(),说简单点就是用于判断对应的磁盘空间是否还够用。
该功能具体作用的地方,一下子找不到的话,我们可以通过搜索上面两个接口来查看调用者。其实就是pkg/kubelet/kubelet.go中的isOutOfDisk()接口:// handleOutOfDisk detects if pods can't fit due to lack of disk space.func (kl *Kubelet) isOutOfDisk() bool { // Check disk space once globally and reject or accept all new pods. withinBounds, err := kl.diskSpaceManager.IsRuntimeDiskSpaceAvailable() // Assume enough space in case of errors. if err != nil { glog.Errorf("Failed to check if disk space is available for the runtime: %v", err) } else if !withinBounds { return true } withinBounds, err = kl.diskSpaceManager.IsRootDiskSpaceAvailable() // Assume enough space in case of errors. if err != nil { glog.Errorf("Failed to check if disk space is available on the root partition: %v", err) } else if !withinBounds { return true } return false}
该接口很简单,就是分别调用diskSpaceManager实现的两个接口,然后判断磁盘空间是否够用。
到这里我们可以猜想一下,最开始介绍该功能的时候已经说了是用于预留磁盘空间,以此为条件来判断Pods是否能成功创建。所以可以想到这个isOutOfDisk()肯定是作用于Pods创建的流程中,在创建之前判断是否有条件可以创建。具体的Pod管理流程内容较多,后面新启一篇文章进行单独介绍。