Path: blob/main/components/ws-daemon/pkg/diskguard/guard.go
2499 views
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package diskguard56import (7"context"8"syscall"9"time"1011"golang.org/x/xerrors"12metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"13"k8s.io/client-go/kubernetes"14"k8s.io/client-go/util/retry"1516"github.com/gitpod-io/gitpod/common-go/log"17"github.com/gitpod-io/gitpod/common-go/util"18)1920const (21// LabelDiskPressure is set on a node if any of the guarded disks have22// too little space available.23LabelDiskPressure = "gitpod.io/diskPressure"24)2526// Config configures the disk guard27type Config struct {28Enabled bool `json:"enabled"`29Interval util.Duration `json:"interval"`30Locations []LocationConfig `json:"locations"`31}3233type LocationConfig struct {34Path string `json:"path"`35MinBytesAvail uint64 `json:"minBytesAvail"`36}3738// FromConfig produces a set of disk space guards from the configuration39func FromConfig(cfg Config, clientset kubernetes.Interface, nodeName string) []*Guard {40if !cfg.Enabled {41return nil42}4344res := make([]*Guard, len(cfg.Locations))45for i, loc := range cfg.Locations {46res[i] = &Guard{47Path: loc.Path,48MinBytesAvail: loc.MinBytesAvail,49Interval: time.Duration(cfg.Interval),50Clientset: clientset,51Nodename: nodeName,52}53}5455return res56}5758// Guard regularly checks how much free space is left on a path/disk.59// If the percentage of used space goes above a certain threshold,60// we'll label the node accordingly - and remove the label once that condition61// subsides.62type Guard struct {63Path string64MinBytesAvail uint6465Interval time.Duration66Clientset kubernetes.Interface67Nodename string68}6970// Start starts the disk guard71func (g *Guard) Start() {72t := time.NewTicker(g.Interval)73defer t.Stop()74for {75bvail, err := getAvailableBytes(g.Path)76if err != nil {77log.WithError(err).WithField("path", g.Path).Error("cannot check how much space is available")78continue79}80log.WithField("bvail", bvail).WithField("minBytesAvail", g.MinBytesAvail).Debug("checked for available disk space")8182addLabel := bvail <= g.MinBytesAvail83err = g.setLabel(LabelDiskPressure, addLabel)84if err != nil {85log.WithError(err).Error("cannot update node label")86}8788<-t.C89}90}9192// setLabel adds or removes the label from the node93func (g *Guard) setLabel(label string, add bool) error {94return retry.RetryOnConflict(retry.DefaultBackoff, func() error {95ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)96defer cancel()9798node, err := g.Clientset.CoreV1().Nodes().Get(ctx, g.Nodename, metav1.GetOptions{})99if err != nil {100return err101}102_, hasLabel := node.Labels[label]103if add == hasLabel {104return nil105}106107if add {108node.Labels[label] = "true"109log.WithField("node", g.Nodename).WithField("label", label).Info("adding label to node")110} else {111delete(node.Labels, label)112log.WithField("node", g.Nodename).WithField("label", label).Info("removing label from node")113}114_, err = g.Clientset.CoreV1().Nodes().Update(ctx, node, metav1.UpdateOptions{})115if err != nil {116return err117}118119return nil120})121}122123func getAvailableBytes(path string) (bvail uint64, err error) {124var stat syscall.Statfs_t125err = syscall.Statfs(path, &stat)126if err != nil {127return 0, xerrors.Errorf("cannot stat %s: %w", path, err)128}129130bvail = stat.Bavail * uint64(stat.Bsize)131return132}133134135