github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/k8s/client.go (about) 1 // Package k8s: initialization, client, and misc. helpers 2 /* 3 * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package k8s 6 7 import ( 8 "context" 9 "io" 10 "os" 11 "strings" 12 13 "github.com/NVIDIA/aistore/api/env" 14 "github.com/NVIDIA/aistore/cmn/debug" 15 corev1 "k8s.io/api/core/v1" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/fields" 18 "k8s.io/client-go/kubernetes" 19 tcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" 20 "k8s.io/client-go/rest" 21 ) 22 23 type ( 24 // Client is simplified version of default `kubernetes.Interface` client. 25 Client interface { 26 Create(v any) error 27 Delete(entityType, entityName string) error 28 CheckExists(entityType, entityName string) (bool, error) 29 Pod(name string) (*corev1.Pod, error) 30 Pods() (*corev1.PodList, error) 31 Service(name string) (*corev1.Service, error) 32 Node(name string) (*corev1.Node, error) 33 Logs(podName string) ([]byte, error) 34 Health(podName string) (string, error) 35 CheckMetricsAvailability() error 36 } 37 38 // defaultClient implements Client interface. 39 defaultClient struct { 40 client kubernetes.Interface 41 config *rest.Config 42 namespace string 43 err error 44 } 45 ) 46 47 var ( 48 _defaultK8sClient *defaultClient 49 ) 50 51 func _initClient() { 52 config, err := rest.InClusterConfig() 53 if err != nil { 54 _defaultK8sClient = &defaultClient{err: err} 55 return 56 } 57 client, err := kubernetes.NewForConfig(config) 58 if err != nil { 59 _defaultK8sClient = &defaultClient{err: err} 60 return 61 } 62 _defaultK8sClient = &defaultClient{ 63 namespace: _namespace(), 64 client: client, 65 config: config, 66 } 67 } 68 69 // Retrieve pod namespace 70 // See: 71 // - topic: "how to get current namespace of an in-cluster go Kubernetes client" 72 // - https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ 73 func _namespace() (namespace string) { 74 // production 75 if namespace = os.Getenv(env.AIS.K8sNamespace); namespace != "" { 76 debug.Func(func() { 77 ns := os.Getenv(defaultNamespaceEnv) 78 debug.Assertf(ns == "" || ns == namespace, "%q vs %q", ns, namespace) 79 }) 80 return 81 } 82 // otherwise, try default env var 83 if namespace = os.Getenv(defaultNamespaceEnv); namespace != "" { 84 return 85 } 86 // finally, last resort kludge 87 if ns, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { 88 if namespace = strings.TrimSpace(string(ns)); namespace != "" { 89 return 90 } 91 } 92 return "default" 93 } 94 95 func GetClient() (Client, error) { 96 if _defaultK8sClient.err != nil { 97 return nil, _defaultK8sClient.err 98 } 99 return _defaultK8sClient, nil 100 } 101 102 /////////////////// 103 // defaultClient // 104 /////////////////// 105 106 func (c *defaultClient) pods() tcorev1.PodInterface { 107 return c.client.CoreV1().Pods(c.namespace) 108 } 109 110 func (c *defaultClient) services() tcorev1.ServiceInterface { 111 return c.client.CoreV1().Services(c.namespace) 112 } 113 114 func (c *defaultClient) Create(v any) (err error) { 115 ctx := context.Background() 116 switch t := v.(type) { 117 case *corev1.Pod: 118 _, err = c.pods().Create(ctx, t, metav1.CreateOptions{}) 119 case *corev1.Service: 120 _, err = c.services().Create(ctx, t, metav1.CreateOptions{}) 121 default: 122 debug.FailTypeCast(v) 123 } 124 return 125 } 126 127 func (c *defaultClient) Delete(entityType, entityName string) (err error) { 128 ctx := context.Background() 129 switch entityType { 130 case Pod: 131 err = c.pods().Delete(ctx, entityName, *metav1.NewDeleteOptions(0)) 132 case Svc: 133 err = c.services().Delete(ctx, entityName, *metav1.NewDeleteOptions(0)) 134 default: 135 debug.Assert(false, "unknown entity type", entityType) 136 } 137 return 138 } 139 140 func (c *defaultClient) CheckExists(entityType, entityName string) (exists bool, err error) { 141 var ( 142 ctx = context.Background() 143 listOptions = metav1.ListOptions{ 144 FieldSelector: fields.OneTermEqualSelector("metadata.name", entityName).String(), 145 } 146 ) 147 switch entityType { 148 case Pod: 149 var pods *corev1.PodList 150 pods, err = c.pods().List(ctx, listOptions) 151 if err != nil { 152 return false, err 153 } 154 if len(pods.Items) == 0 { 155 return false, nil 156 } 157 case Svc: 158 var services *corev1.ServiceList 159 services, err = c.services().List(ctx, listOptions) 160 if err != nil { 161 return false, err 162 } 163 if len(services.Items) == 0 { 164 return false, nil 165 } 166 default: 167 debug.Assert(false, "unknown entity type", entityType) 168 } 169 return true, nil 170 } 171 172 func (c *defaultClient) Pod(name string) (*corev1.Pod, error) { 173 return c.pods().Get(context.Background(), name, metav1.GetOptions{}) 174 } 175 176 func (c *defaultClient) Pods() (*corev1.PodList, error) { 177 return c.pods().List(context.Background(), metav1.ListOptions{}) 178 } 179 180 func (c *defaultClient) Service(name string) (*corev1.Service, error) { 181 return c.services().Get(context.Background(), name, metav1.GetOptions{}) 182 } 183 184 func (c *defaultClient) Node(name string) (*corev1.Node, error) { 185 return c.client.CoreV1().Nodes().Get(context.Background(), name, metav1.GetOptions{}) 186 } 187 188 func (c *defaultClient) Logs(podName string) ([]byte, error) { 189 logStream, err := c.pods().GetLogs(podName, &corev1.PodLogOptions{}).Stream(context.Background()) 190 if err != nil { 191 return nil, err 192 } 193 defer logStream.Close() 194 return io.ReadAll(logStream) 195 } 196 197 func (c *defaultClient) CheckMetricsAvailability() error { 198 _, err := c.client.CoreV1().RESTClient().Get().AbsPath("/apis/metrics.k8s.io/v1beta1/pods").DoRaw(context.Background()) 199 return err 200 } 201 202 func (c *defaultClient) Health(podName string) (string, error) { 203 response, err := c.pods().Get(context.Background(), podName, metav1.GetOptions{}) 204 if err != nil { 205 return "Error", err 206 } 207 return string(response.Status.Phase), nil 208 }