github.com/Azure/aad-pod-identity@v1.8.17/pkg/k8s/client.go (about) 1 package k8s 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "os" 8 "strings" 9 "time" 10 11 aadpodid "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity" 12 crd "github.com/Azure/aad-pod-identity/pkg/crd" 13 "github.com/Azure/aad-pod-identity/pkg/metrics" 14 "github.com/Azure/aad-pod-identity/version" 15 16 v1 "k8s.io/api/core/v1" 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 informersv1 "k8s.io/client-go/informers/core/v1" 19 "k8s.io/client-go/informers/internalinterfaces" 20 "k8s.io/client-go/kubernetes" 21 "k8s.io/client-go/rest" 22 "k8s.io/client-go/tools/cache" 23 "k8s.io/client-go/tools/clientcmd" 24 "k8s.io/klog/v2" 25 ) 26 27 const ( 28 getPodListRetries = 4 29 getPodListSleepTimeMilliseconds = 300 30 ) 31 32 // Client api client 33 type Client interface { 34 // Start just starts any informers required. 35 Start(<-chan struct{}) 36 // GetPod returns the pod object based on name and namespce 37 GetPod(namespace, name string) (v1.Pod, error) 38 // GetPodInfo returns the pod name, namespace & replica set name for a given pod ip 39 GetPodInfo(podip string) (podns, podname, rsName string, selectors *metav1.LabelSelector, err error) 40 // ListPodIds pod matching azure identity or nil 41 ListPodIds(podns, podname string) (map[string][]aadpodid.AzureIdentity, error) 42 // ListPodIdsWithBinding pod matching azure identity or nil 43 ListPodIdsWithBinding(podns string, labels map[string]string) ([]aadpodid.AzureIdentity, error) 44 // GetSecret returns secret the secretRef represents 45 GetSecret(secretRef *v1.SecretReference) (*v1.Secret, error) 46 // ListPodIdentityExceptions returns list of azurepodidentityexceptions 47 ListPodIdentityExceptions(namespace string) (*[]aadpodid.AzurePodIdentityException, error) 48 } 49 50 // KubeClient k8s client 51 type KubeClient struct { 52 // Main Kubernetes client 53 ClientSet kubernetes.Interface 54 // Crd client used to access our CRD resources. 55 CrdClient *crd.Client 56 PodInformer cache.SharedIndexInformer 57 reporter *metrics.Reporter 58 } 59 60 // NewKubeClient new kubernetes api client 61 func NewKubeClient(nodeName string, scale, isStandardMode bool) (Client, error) { 62 config, err := buildConfig() 63 if err != nil { 64 return nil, err 65 } 66 config.UserAgent = version.GetUserAgent("NMI", version.NMIVersion) 67 clientset, err := getkubeclient(config) 68 if err != nil { 69 return nil, err 70 } 71 crdclient, err := crd.NewCRDClientLite(config, nodeName, scale, isStandardMode) 72 if err != nil { 73 return nil, err 74 } 75 reporter, err := metrics.NewReporter() 76 if err != nil { 77 return nil, fmt.Errorf("failed to create reporter for metrics, error: %+v", err) 78 } 79 80 podInformer := informersv1.NewFilteredPodInformer(clientset, v1.NamespaceAll, 10*time.Minute, 81 cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, 82 NodeNameFilter(nodeName)) 83 84 kubeClient := &KubeClient{ 85 CrdClient: crdclient, 86 ClientSet: clientset, 87 PodInformer: podInformer, 88 reporter: reporter, 89 } 90 91 return kubeClient, nil 92 } 93 94 // Sync syncs the cache from the K8s client. 95 func (c *KubeClient) Sync(exit <-chan struct{}) { 96 if !cache.WaitForCacheSync(exit, c.PodInformer.HasSynced) { 97 klog.Error("pod cache could not be synchronized") 98 } 99 } 100 101 // Start the corresponding starts 102 func (c *KubeClient) Start(exit <-chan struct{}) { 103 go c.PodInformer.Run(exit) 104 c.CrdClient.StartLite(exit) 105 c.Sync(exit) 106 } 107 108 func (c *KubeClient) getReplicasetName(pod v1.Pod) string { 109 for _, owner := range pod.OwnerReferences { 110 if strings.EqualFold(owner.Kind, "ReplicaSet") { 111 return owner.Name 112 } 113 } 114 return "" 115 } 116 117 // NodeNameFilter will tweak the options to include the node name as field 118 // selector. 119 func NodeNameFilter(nodeName string) internalinterfaces.TweakListOptionsFunc { 120 return func(l *metav1.ListOptions) { 121 if l == nil { 122 l = &metav1.ListOptions{} 123 } 124 l.FieldSelector = l.FieldSelector + "spec.nodeName=" + nodeName 125 } 126 } 127 128 // GetPod returns pod that matches namespace and name 129 func (c *KubeClient) GetPod(namespace, name string) (v1.Pod, error) { 130 // TODO (aramase) wrap this with retries 131 obj, exists, err := c.PodInformer.GetIndexer().GetByKey(namespace + "/" + name) 132 if err != nil { 133 return v1.Pod{}, fmt.Errorf("failed to get pod %s/%s, error: %+v", namespace, name, err) 134 } 135 if !exists { 136 return v1.Pod{}, fmt.Errorf("pod %s/%s doesn't exist", namespace, name) 137 } 138 pod, ok := obj.(*v1.Pod) 139 if !ok { 140 return v1.Pod{}, fmt.Errorf("could not cast %T to v1.Pod", pod) 141 } 142 return *pod, nil 143 } 144 145 // GetPodInfo get pod ns,name from apiserver 146 func (c *KubeClient) GetPodInfo(podip string) (string, string, string, *metav1.LabelSelector, error) { 147 if podip == "" { 148 return "", "", "", nil, fmt.Errorf("pod IP is empty") 149 } 150 151 podList, err := c.getPodListRetry(podip, getPodListRetries, getPodListSleepTimeMilliseconds) 152 153 if err != nil { 154 return "", "", "", nil, err 155 } 156 numMatching := len(podList) 157 if numMatching == 1 { 158 return podList[0].Namespace, podList[0].Name, c.getReplicasetName(*podList[0]), &metav1.LabelSelector{ 159 MatchLabels: podList[0].Labels}, nil 160 } 161 162 return "", "", "", nil, fmt.Errorf("failed to match pod IP %s with %v", podip, podList) 163 } 164 165 func isPhaseValid(p v1.PodPhase) bool { 166 return p == v1.PodPending || p == v1.PodRunning 167 } 168 169 func (c *KubeClient) getPodList(podip string) ([]*v1.Pod, error) { 170 var podList []*v1.Pod 171 list := c.PodInformer.GetStore().List() 172 for _, o := range list { 173 pod, ok := o.(*v1.Pod) 174 if !ok { 175 err := fmt.Errorf("could not cast %T to v1.Pod", pod) 176 return nil, err 177 } 178 if pod.Status.PodIP == podip && isPhaseValid(pod.Status.Phase) { 179 podList = append(podList, pod) 180 } 181 } 182 if len(podList) == 0 { 183 return nil, fmt.Errorf("pod list is empty") 184 } 185 return podList, nil 186 } 187 188 func (c *KubeClient) getPodListRetry(podip string, retries int, sleeptime time.Duration) ([]*v1.Pod, error) { 189 var podList []*v1.Pod 190 var err error 191 i := 0 192 193 for { 194 // Atleast run the getpodlist once. 195 podList, err = c.getPodList(podip) 196 if err == nil { 197 return podList, nil 198 } 199 merr := c.reporter.ReportKubernetesAPIOperationError(metrics.GetPodListOperationName) 200 if merr != nil { 201 klog.Warningf("failed to report metrics, error: %+v", merr) 202 } 203 204 if i >= retries { 205 break 206 } 207 i++ 208 klog.Warningf("list pod error: %+v. Retrying, attempt number: %d", err, i) 209 time.Sleep(sleeptime * time.Millisecond) 210 } 211 // We reach here only if there is an error and we have exhausted all retries. 212 // Return the last error 213 return nil, err 214 } 215 216 // GetLocalIP returns the non loopback local IP of the host 217 func GetLocalIP() (string, error) { 218 addrs, err := net.InterfaceAddrs() 219 if err != nil { 220 return "", err 221 } 222 for _, address := range addrs { 223 // check the address type and if it is not a loopback 224 if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 225 if ipnet.IP.To4() != nil { 226 return ipnet.IP.String(), nil 227 } 228 } 229 } 230 return "", fmt.Errorf("non loopback IP address not found") 231 } 232 233 // ListPodIds lists matching ids for pod or error 234 func (c *KubeClient) ListPodIds(podns, podname string) (map[string][]aadpodid.AzureIdentity, error) { 235 return c.CrdClient.ListPodIds(podns, podname) 236 } 237 238 // ListPodIdsWithBinding list matching ids for pod based on the bindings 239 func (c *KubeClient) ListPodIdsWithBinding(podns string, labels map[string]string) ([]aadpodid.AzureIdentity, error) { 240 return c.CrdClient.GetPodIDsWithBinding(podns, labels) 241 } 242 243 // ListPodIdentityExceptions lists azurepodidentityexceptions 244 func (c *KubeClient) ListPodIdentityExceptions(ns string) (*[]aadpodid.AzurePodIdentityException, error) { 245 return c.CrdClient.ListPodIdentityExceptions(ns) 246 } 247 248 // GetSecret returns secret the secretRef represents 249 func (c *KubeClient) GetSecret(secretRef *v1.SecretReference) (*v1.Secret, error) { 250 secret, err := c.ClientSet.CoreV1().Secrets(secretRef.Namespace).Get(context.TODO(), secretRef.Name, metav1.GetOptions{}) 251 if err != nil { 252 merr := c.reporter.ReportKubernetesAPIOperationError(metrics.GetSecretOperationName) 253 if merr != nil { 254 klog.Warningf("failed to report metrics, error: %+v", err) 255 } 256 return nil, err 257 } 258 return secret, nil 259 } 260 261 func getkubeclient(config *rest.Config) (*kubernetes.Clientset, error) { 262 // creates the clientset 263 kubeClient, err := kubernetes.NewForConfig(config) 264 if err != nil { 265 return nil, err 266 } 267 268 return kubeClient, err 269 } 270 271 // Create the client config. Use kubeconfig if given, otherwise assume in-cluster. 272 func buildConfig() (*rest.Config, error) { 273 kubeconfigPath := os.Getenv("KUBECONFIG") 274 if kubeconfigPath != "" { 275 return clientcmd.BuildConfigFromFlags("", kubeconfigPath) 276 } 277 278 return rest.InClusterConfig() 279 }