package cloud import ( "log" "os" "golang.org/x/net/context" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" ) var ( quotaGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "resourcequotas"} limitRangeGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "limitranges"} ) // ApplyResourceQuota создаёт ResourceQuota и LimitRange в namespace ns. // Параметры берутся из env vars (см. ниже) — можно тюнить без пересборки. // Идемпотентно: если объекты уже существуют — пропускается без ошибки. // // ResourceQuota ограничивает суммарные ресурсы пользователя: // - CPU / memory requests и limits // - Количество подов, функций, пакетов, triggers // // LimitRange задаёт дефолтные limits/requests на контейнер: // поды без явных limits получают defaults автоматически → нет unbounded containers. // // Env vars (все опциональны, defaults указаны ниже): // // QUOTA_REQ_CPU — суммарный CPU requests (default: "1") // QUOTA_REQ_MEM — суммарная memory requests (default: "2Gi") // QUOTA_LIM_CPU — суммарный CPU limits (default: "12") // QUOTA_LIM_MEM — суммарная memory limits (default: "6Gi") // QUOTA_PODS — макс. количество подов (default: "30") // QUOTA_FUNCTIONS — макс. количество функций (default: "20") // QUOTA_PACKAGES — макс. количество пакетов (default: "40") // QUOTA_HTTPTRIGGERS — макс. количество HTTP triggers (default: "20") // LIMIT_DEFAULT_CPU — дефолтный CPU limit на контейнер (default: "500m") // LIMIT_DEFAULT_MEM — дефолтная memory limit на контейнер (default: "256Mi") // LIMIT_REQ_CPU — дефолтный CPU request на контейнер (default: "50m") // LIMIT_REQ_MEM — дефолтный memory request на контейнер (default: "64Mi") // LIMIT_MAX_CPU — максимальный CPU limit на контейнер (default: "2") // LIMIT_MAX_MEM — максимальная memory limit на контейнер (default: "1Gi") func ApplyResourceQuota(ctx context.Context, dyn dynamic.Interface, ns string) { quotaObj := &unstructured.Unstructured{Object: map[string]any{ "apiVersion": "v1", "kind": "ResourceQuota", "metadata": map[string]any{"name": "user-quota", "namespace": ns}, "spec": map[string]any{ "hard": map[string]any{ "requests.cpu": envDefault("QUOTA_REQ_CPU", "1"), "requests.memory": envDefault("QUOTA_REQ_MEM", "2Gi"), "limits.cpu": envDefault("QUOTA_LIM_CPU", "12"), "limits.memory": envDefault("QUOTA_LIM_MEM", "6Gi"), "pods": envDefault("QUOTA_PODS", "30"), "count/functions.fission.io": envDefault("QUOTA_FUNCTIONS", "20"), "count/packages.fission.io": envDefault("QUOTA_PACKAGES", "40"), "count/httptriggers.fission.io": envDefault("QUOTA_HTTPTRIGGERS", "20"), }, }, }} _, err := dyn.Resource(quotaGVR).Namespace(ns).Create(ctx, quotaObj, metav1.CreateOptions{}) if err != nil && !apierrors.IsAlreadyExists(err) { log.Printf("cloud.ApplyResourceQuota: %s: %v", ns, err) } limitRangeObj := &unstructured.Unstructured{Object: map[string]any{ "apiVersion": "v1", "kind": "LimitRange", "metadata": map[string]any{"name": "user-limits", "namespace": ns}, "spec": map[string]any{ "limits": []any{ map[string]any{ "type": "Container", "default": map[string]any{ "cpu": envDefault("LIMIT_DEFAULT_CPU", "500m"), "memory": envDefault("LIMIT_DEFAULT_MEM", "256Mi"), }, "defaultRequest": map[string]any{ "cpu": envDefault("LIMIT_REQ_CPU", "50m"), "memory": envDefault("LIMIT_REQ_MEM", "64Mi"), }, "max": map[string]any{ "cpu": envDefault("LIMIT_MAX_CPU", "2"), "memory": envDefault("LIMIT_MAX_MEM", "1Gi"), }, }, }, }, }} _, err = dyn.Resource(limitRangeGVR).Namespace(ns).Create(ctx, limitRangeObj, metav1.CreateOptions{}) if err != nil && !apierrors.IsAlreadyExists(err) { log.Printf("cloud.ApplyResourceQuota LimitRange: %s: %v", ns, err) } } // envDefault читает env переменную или возвращает fallback. func envDefault(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }